diff --git a/fusee_cpp/program/source/mtc/fusee_mtc.cpp b/fusee_cpp/program/source/mtc/fusee_mtc.cpp index 0bbeafe36..b962cd638 100644 --- a/fusee_cpp/program/source/mtc/fusee_mtc.cpp +++ b/fusee_cpp/program/source/mtc/fusee_mtc.cpp @@ -18,12 +18,16 @@ namespace ams::nxboot { void DoMemoryTrainingErista(int index, void *mtc_tables_buffer); - void DoMemoryTrainingMariko(int index, void *mtc_tables_buffer); + void DoMemoryTrainingMariko(bool *out_did_training, int index, void *mtc_tables_buffer); + + void RestoreMemoryClockRateMariko(void *mtc_tables_buffer); namespace { alignas(4) constinit u8 g_mtc_tables_buffer[0x26C0]; + constinit bool g_did_training_mariko = false; + constexpr const u8 MemoryTrainingTableIndex_Invalid = std::numeric_limits::max(); constexpr const u8 MemoryTrainingTableIndices[] = { @@ -74,7 +78,14 @@ namespace ams::nxboot { if (fuse::GetSocType() == fuse::SocType_Erista) { DoMemoryTrainingErista(index, g_mtc_tables_buffer); } else { - DoMemoryTrainingMariko(index, g_mtc_tables_buffer); + DoMemoryTrainingMariko(std::addressof(g_did_training_mariko), index, g_mtc_tables_buffer); + } + } + + void RestoreMemoryClockRate() { + /* NOTE: This resolves an off-by-one issue in PCV's detection of memory clock rate on Mariko. */ + if (fuse::GetSocType() == fuse::SocType_Mariko && g_did_training_mariko) { + RestoreMemoryClockRateMariko(g_mtc_tables_buffer); } } diff --git a/fusee_cpp/program/source/mtc/fusee_mtc.hpp b/fusee_cpp/program/source/mtc/fusee_mtc.hpp index 2fc9e120c..7367a0344 100644 --- a/fusee_cpp/program/source/mtc/fusee_mtc.hpp +++ b/fusee_cpp/program/source/mtc/fusee_mtc.hpp @@ -20,4 +20,6 @@ namespace ams::nxboot { void DoMemoryTraining(); + void RestoreMemoryClockRate(); + } diff --git a/fusee_cpp/program/source/mtc/fusee_mtc_mariko.cpp b/fusee_cpp/program/source/mtc/fusee_mtc_mariko.cpp index 0980d4eaa..73bb500e7 100644 --- a/fusee_cpp/program/source/mtc/fusee_mtc_mariko.cpp +++ b/fusee_cpp/program/source/mtc/fusee_mtc_mariko.cpp @@ -23,8 +23,53 @@ namespace ams::nxboot { namespace { + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t MC = MC_BASE; + constexpr inline const uintptr_t EMC = EMC_BASE; + constexpr inline const uintptr_t EMC0 = EMC0_BASE; + constexpr inline const uintptr_t EMC1 = EMC1_BASE; + + static constinit bool g_next_pll = false; + static constinit bool g_did_first_training = false; + static constinit bool g_fsp_for_next_freq = false; + #include "fusee_mtc_tables_mariko.inc" + #define DECLARE_OFFSET_HANDLER(BASE, REG, NAME) REG, + #define DECLARE_REGISTER_HANDLER(BASE, REG, NAME) BASE + REG, + + constexpr inline const u16 BurstRegistersOffsets[] = { + FOREACH_BURST_REG(DECLARE_OFFSET_HANDLER) + }; + + constexpr inline const u32 TrimRegisters[] = { + FOREACH_TRIM_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 BurstMcRegisters[] = { + FOREACH_BURST_MC_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 LaScaleRegisters[] = { + FOREACH_LA_SCALE_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelTrimRegisters[] = { + FOREACH_PER_CHANNEL_TRIM_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelBurstRegisters[] = { + FOREACH_PER_CHANNEL_BURST_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelVrefRegisters[] = { + FOREACH_PER_CHANNEL_VREF_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelTrainingModRegisters[] = { + FOREACH_PER_CHANNEL_TRAINING_MOD_REG(DECLARE_REGISTER_HANDLER) + }; + using EmcDvfsTimingTable = mariko::EmcDvfsTimingTable; EmcDvfsTimingTable *GetEmcDvfsTimingTables(int index, void *mtc_tables_buffer) { @@ -61,20 +106,228 @@ namespace ams::nxboot { return out_tables; } - } - - void DoMemoryTrainingMariko(int index, void *mtc_tables_buffer) { - /* Get timing tables. */ - auto *timing_tables = GetEmcDvfsTimingTables(index, mtc_tables_buffer); - auto *src_timing_tables = timing_tables + 0; - auto *dst_timing_tables = timing_tables + 1; - - /* Check timing tables. */ - if (src_timing_tables->rate_khz != 204000 || dst_timing_tables->rate_khz != 1600000) { - ShowFatalError("EmcDvfsTimingTables seem corrupted %" PRIu32 " %" PRIu32 "?\n", src_timing_tables->rate_khz, dst_timing_tables->rate_khz); + bool IsSamePll(u32 next_2x, u32 prev_2x) { + if (next_2x == prev_2x) { + return true; + } else if ((next_2x == PLLM_OUT0 || next_2x == PLLM_UD) && (prev_2x == PLLM_OUT0 || prev_2x == PLLM_UD)) { + return true; + } else { + return false; + } } - /* TODO */ + bool PllReprogram(u32 next_rate_khz, u32 next_clk_src, u32 prev_rate_khz, u32 prev_clk_src) { + /* Get current divp value. */ + u32 pll_p; + switch (reg::GetValue(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLM_UD: + case PLLM_OUT0: + pll_p = reg::GetValue(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, CLK_RST_REG_BITS_MASK(PLLM_BASE_PLLM_DIVP_B01)); + break; + case PLLMB_UD: + case PLLMB_OUT0: + pll_p = reg::GetValue(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, CLK_RST_REG_BITS_MASK(PLLMB_BASE_PLLMB_DIVP_B01)); + break; + default: + pll_p = 0; + break; + } + + /* Get clk src/divisor. */ + const u32 next_2x = reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + const u32 prev_2x = reg::GetField(prev_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + u32 next_div = reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR)); + u32 prev_div = reg::GetField(prev_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR)); + + /* Update divisor, if necessary. */ + if (next_2x == PLLM_UD || next_2x == PLLMB_UD) { + next_div = 0; + } + if (prev_2x == PLLM_UD || prev_2x == PLLMB_UD) { + prev_div = 0; + } + + /* If the pll is different, reprogramming is necessary. */ + if (!IsSamePll(next_2x, prev_2x)) { + return true; + } + + /* Return whether the ratios are different. */ + const float next_freq = next_rate_khz * (1 + (next_div >> 1) + (0.5 * (next_div & 1))) * (pll_p + 1); + const float prev_freq = prev_rate_khz * (1 + (prev_div >> 1) + (0.5 * (prev_div & 1))) * (pll_p + 1); + + const float ratio = prev_freq / next_freq; + + return ratio > 1.01 || ratio < 0.99; + } + + u32 ProgramPllm(u32 next_rate_khz, u32 next_clk_src, u32 ret_clk_src, bool is_pllmb, EmcDvfsTimingTable *timing_tables) { + u32 ret = ret_clk_src; + + const uint32_t base = ((timing_tables->pllmb_divm & 0xFF) | ((timing_tables->pllmb_divn & 0xFF) << 8) | ((timing_tables->pllmb_divp & 1) << 20)); + if (is_pllmb) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, base); + reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE); + + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLMB_MISC1, 0x10000000); + + if (timing_tables->pll_en_ssc & 1) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CFG, timing_tables->pllmb_ss_cfg); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CTRL1, timing_tables->pllmb_ss_ctrl1); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CTRL2, timing_tables->pllmb_ss_ctrl2); + } else { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CFG, timing_tables->pllmb_ss_cfg & 0xBFFFFFFF); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CTRL2, timing_tables->pllmb_ss_ctrl2 & 0x0000FFFF); + } + + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, 0x40000000); + + switch (reg::GetField(ret, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLM_OUT0: + reg::SetField(ret, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_OUT0)); + break; + case PLLM_UD: + reg::SetField(ret, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_UD)); + break; + } + + while ((reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE) & 0x8000000) == 0) { /* ... */ } + + return ret; + } else { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, base); + reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE); + + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC2, 0x10); + + if (timing_tables->pll_en_ssc & 1) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CFG, timing_tables->pllm_ss_cfg); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CTRL1, timing_tables->pllm_ss_ctrl1); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CTRL2, timing_tables->pllm_ss_ctrl2); + } else { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CFG, timing_tables->pllm_ss_cfg & 0xBFFFFFFF); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CTRL2, timing_tables->pllm_ss_ctrl2 & 0x0000FFFF); + } + + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, 0x40000000); + + switch (reg::GetField(ret, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLM_OUT0: + reg::SetField(ret, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLM_OUT0)); + break; + case PLLM_UD: + reg::SetField(ret, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLM_UD)); + break; + } + + while ((reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE) & 0x8000000) == 0) { /* ... */ } + + return ret; + } + } + + void FreqChange(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 training, u32 next_clk_src) { + /* TODO */ + } + + void TrainFreq(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 next_clk_src) { + /* TODO */ + } + + void Dvfs(EmcDvfsTimingTable *dst_timing, EmcDvfsTimingTable *src_timing, bool train) { + /* Get the clock sources/rates. */ + u32 clk_src_emc_from = src_timing->clk_src_emc; + u32 clk_src_emc_to = dst_timing->clk_src_emc; + u32 rate_from = src_timing->rate_khz; + u32 rate_to = dst_timing->rate_khz; + + /* Get channel enables. */ + const bool ch0_enable = reg::GetField(dst_timing->emc_fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH0_ENABLE)) == EMC_FBIO_CFG7_CH0_ENABLE_ENABLE; + const bool ch1_enable = reg::GetField(dst_timing->emc_fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH1_ENABLE)) == EMC_FBIO_CFG7_CH1_ENABLE_ENABLE; + + /* Reprogram pll. */ + const u32 prev_2x_clk_src = reg::GetField(clk_src_emc_from, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + const u32 next_2x_clk_src = reg::GetField(clk_src_emc_to, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + if (next_2x_clk_src != PLLP_OUT0 && next_2x_clk_src != PLLP_UD) { + if (ch0_enable || ch1_enable) { + if (PllReprogram(rate_to, clk_src_emc_to, rate_from, clk_src_emc_from)) { + if (prev_2x_clk_src == PLLMB_UD || prev_2x_clk_src == PLLMB_OUT0) { + g_next_pll = 0; + } else if (prev_2x_clk_src == PLLM_UD || prev_2x_clk_src == PLLM_OUT0) { + g_next_pll = !g_next_pll; + } + + clk_src_emc_to = ProgramPllm(rate_to, clk_src_emc_to, clk_src_emc_to, g_next_pll, dst_timing); + } else { + if (next_2x_clk_src == PLLM_UD || next_2x_clk_src == PLLMB_UD) { + if (g_next_pll) { + reg::SetField(clk_src_emc_to, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_UD)); + } + } else if (next_2x_clk_src == PLLM_OUT0 || next_2x_clk_src == PLLMB_OUT0) { + if (g_next_pll) { + reg::SetField(clk_src_emc_to, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_OUT0)); + } + } + } + } + } + + if (train) { + TrainFreq(src_timing, dst_timing, clk_src_emc_to); + if (ch0_enable || ch1_enable) { + if (PllReprogram(dst_timing->rate_khz, dst_timing->clk_src_emc, src_timing->rate_khz, src_timing->clk_src_emc)) { + g_next_pll = !g_next_pll; + } + } + } else { + FreqChange(src_timing, dst_timing, 0, clk_src_emc_to); + } + } + + } + + void DoMemoryTrainingMariko(bool *out_did_training, int index, void *mtc_tables_buffer) { + /* Get timing tables. */ + auto *timing_tables = GetEmcDvfsTimingTables(index, mtc_tables_buffer); + auto *src_timing = timing_tables + 0; + auto *dst_timing = timing_tables + 1; + + /* Check timing tables. */ + if (src_timing->rate_khz != 204000 || dst_timing->rate_khz != 1600000) { + ShowFatalError("EmcDvfsTimingTables seem corrupted %" PRIu32 " %" PRIu32 "?\n", src_timing->rate_khz, dst_timing->rate_khz); + } + + /* Check that we should do training. */ + if (src_timing->clk_src_emc != reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC)) { + /* Our clock source isn't what's expected, so presumably training has already been done? */ + /* Either way, the safe bet is to skip it. */ + *out_did_training = false; + return; + } + + /* Train 1600MHz. */ + Dvfs(dst_timing, src_timing, true); + + /* Switch to 1600MHz. */ + Dvfs(dst_timing, src_timing, false); + + /* Set ourselves as having done training */ + *out_did_training = true; + } + + void RestoreMemoryClockRateMariko(void *mtc_tables_buffer) { + /* Get timing tables. */ + auto *timing_tables = reinterpret_cast(mtc_tables_buffer); + auto *src_timing = timing_tables + 0; + auto *dst_timing = timing_tables + 1; + + /* Check timing tables. */ + if (src_timing->rate_khz != 204000 || dst_timing->rate_khz != 1600000) { + ShowFatalError("EmcDvfsTimingTables seem corrupted %" PRIu32 " %" PRIu32 "?\n", src_timing->rate_khz, dst_timing->rate_khz); + } + + /* Switch to 204MHz */ + Dvfs(src_timing, dst_timing, false); } } diff --git a/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp b/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp index 5911f56fe..d8992d918 100644 --- a/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp @@ -67,6 +67,15 @@ #define CLK_RST_CONTROLLER_PLLC4_BASE (0x5A4) #define CLK_RST_CONTROLLER_PLLC_MISC2 (0x5D0) #define CLK_RST_CONTROLLER_PLLMB_BASE (0x5E8) +#define CLK_RST_CONTROLLER_PLLMB_MISC1 (0x5EC) + +/* Mariko. */ +#define CLK_RST_CONTROLLER_PLLM_SS_CFG (0x774) +#define CLK_RST_CONTROLLER_PLLM_SS_CTRL1 (0x778) +#define CLK_RST_CONTROLLER_PLLM_SS_CTRL2 (0x77C) +#define CLK_RST_CONTROLLER_PLLMB_SS_CFG (0x780) +#define CLK_RST_CONTROLLER_PLLMB_SS_CTRL1 (0x784) +#define CLK_RST_CONTROLLER_PLLMB_SS_CTRL2 (0x788) #define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA (0x0F8) #define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB (0x0FC) @@ -118,6 +127,7 @@ DEFINE_CLK_RST_REG_BIT_ENUM(PLLC_OUT_PLLC_OUT1_DIV_BYP, 16, DISABLE, ENABLE); DEFINE_CLK_RST_REG(PLLM_BASE_PLLM_DIVM, 0, 8); DEFINE_CLK_RST_REG(PLLM_BASE_PLLM_DIVN, 8, 8); DEFINE_CLK_RST_REG(PLLM_BASE_PLLM_DIVP, 20, 5); +DEFINE_CLK_RST_REG(PLLM_BASE_PLLM_DIVP_B01, 20, 1); DEFINE_CLK_RST_REG_BIT_ENUM(PLLM_BASE_PLLM_LOCK, 27, NOT_LOCK, LOCK); DEFINE_CLK_RST_REG_BIT_ENUM(PLLM_BASE_PLLM_REF_DIS, 29, REF_ENABLE, REF_DISABLE); DEFINE_CLK_RST_REG_BIT_ENUM(PLLM_BASE_PLLM_ENABLE, 30, DISABLE, ENABLE); @@ -167,6 +177,7 @@ DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_ENABLE, 30, DISABLE, ENABLE); DEFINE_CLK_RST_REG(PLLMB_BASE_PLLMB_DIVM, 0, 8); DEFINE_CLK_RST_REG(PLLMB_BASE_PLLMB_DIVN, 8, 8); DEFINE_CLK_RST_REG(PLLMB_BASE_PLLMB_DIVP, 20, 5); +DEFINE_CLK_RST_REG(PLLMB_BASE_PLLMB_DIVP_B01, 20, 1); DEFINE_CLK_RST_REG_BIT_ENUM(PLLMB_BASE_PLLMB_LOCK, 27, NOT_LOCK, LOCK); DEFINE_CLK_RST_REG_BIT_ENUM(PLLMB_BASE_PLLMB_ENABLE, 30, DISABLE, ENABLE); diff --git a/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp b/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp index 9c42c9148..2b513658e 100644 --- a/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp @@ -242,6 +242,7 @@ /* Mariko */ #define EMC_PMACRO_DLL_CFG_0 (0x5E4) #define EMC_PMACRO_DLL_CFG_1 (0x5E8) +#define EMC_PMACRO_DLL_CFG_2 (0x5F8) #define EMC_CONFIG_SAMPLE_DELAY (0x5F0) #define EMC_CFG_UPDATE (0x5F4)