From 923dc00b53647e2d3f59bc77f0bb500f4a8d533b Mon Sep 17 00:00:00 2001 From: souldbminersmwc Date: Thu, 2 Oct 2025 12:10:42 -0400 Subject: [PATCH] loader: fix mariko cpu unsafe freqs --- .../loader/source/oc/customize.cpp | 20 +- .../stratosphere/loader/source/oc/oc_test.cpp | 1 + .../loader/source/oc/pcv/pcv_mariko.cpp | 952 ++++++++++-------- 3 files changed, 519 insertions(+), 454 deletions(-) diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp index 38308077..eed560a4 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp @@ -422,14 +422,14 @@ volatile CustomizeTable C = { }, .marikoCpuDvfsTableUnsafeFreqs = { - { 204000, { 732856, -17335, 113 }, { 1120000 } }, - { 306000, { 760024, -18195, 113 }, { 1120000 } }, - { 408000, { 789258, -19055, 113 }, { 1120000 } }, - { 510000, { 789258, -19055, 113 }, { 1120000 } }, - { 612000, { 853926, -20775, 113 }, { 1120000 } }, - { 714000, { 889361, -21625, 113 }, { 1120000 } }, - { 816000, { 926862, -22485, 113 }, { 1120000 } }, - { 918000, { 966431, -23345, 113 }, { 1120000 } }, + // { 204000, { 732856, -17335, 113 }, {} }, // Unneeded, made to make room for new freqs + // { 306000, { 760024, -18195, 113 }, {} }, + { 408000, { 789258, -19055, 113 }, {} }, + { 510000, { 789258, -19055, 113 }, {} }, + { 612000, { 853926, -20775, 113 }, {} }, + { 714000, { 889361, -21625, 113 }, {} }, + { 816000, { 926862, -22485, 113 }, {} }, + { 918000, { 966431, -23345, 113 }, {} }, { 1020000, { 1008066, -24205, 113 }, { 1120000 } }, { 1122000, { 1051768, -25065, 113 }, { 1120000 } }, { 1224000, { 1097537, -25925, 113 }, { 1120000 } }, @@ -447,10 +447,10 @@ volatile CustomizeTable C = { { 2397000, { 1702903, -34955, 113 }, { 1235000 } }, { 2499000, { 1754400, -35643, 113 }, { 1235000 } }, { 2601000, { 1805897, -36331, 113 }, { 1235000 } }, - { 2703000, { 1857394, -37019, 113 }, { 1235000 } }, + { 2703000, { 1857394, -37019, 113 }, { 1235000 } }, { 2805000, { 1908891, -37707, 113 }, { 1235000 } }, { 2907000, { 1960388, -38395, 113 }, { 1235000 } }, -}, + }, .eristaCpuDvfsTableUnsafeFreqs = { { 204000, { 721094 }, {} }, { 306000, { 754040 }, {} }, diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp index 443275a3..8645002e 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp @@ -76,6 +76,7 @@ Result Test_PcvDvfsTable() { cvb_entry_t last_mariko_cpu_cvb_entry_default = { 1963500, { 1675751, -38635, 27 }, { 1120000 } }; assert(memcmp(GetDvfsTableLastEntry((cvb_entry_t *)(&mariko::CpuCvbTableDefault)), (void *)&last_mariko_cpu_cvb_entry_default, sizeof(last_mariko_cpu_cvb_entry_default)) == 0); assert(GetDvfsTableLastEntry((cvb_entry_t *)(&erista::GpuCvbTableDefault))->freq == 921600); + assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoCpuDvfsTableSLT)) == 25); // Customized table default assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.eristaCpuDvfsTable)) == 19); diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp index 06d1f832..de30b352 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp @@ -18,150 +18,167 @@ * along with this program. If not, see . */ - #include "pcv.hpp" - #include "../mtc_timing_value.hpp" +#include "pcv.hpp" +#include "../mtc_timing_value.hpp" - namespace ams::ldr::oc::pcv::mariko { - -Result GpuVmin(u32 *ptr) +namespace ams::ldr::oc::pcv::mariko { - if (!C.marikoGpuVmin) - R_SKIP(); - PATCH_OFFSET(ptr, (int)C.marikoGpuVmin); - R_SUCCEED(); -} -Result GpuVmax(u32 *ptr) -{ - if (!C.marikoGpuVmax) - R_SKIP(); - PATCH_OFFSET(ptr, (int)C.marikoGpuVmax); - R_SUCCEED(); -} - -Result CpuFreqVdd(u32* ptr) { - dvfs_rail* entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(dvfs_rail, freq)); - - R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry()); - R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry()); - R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry()); - R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry()); - - if (C.marikoCpuUV) { - if(!C.enableMarikoCpuUnsafeFreqs) { - PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq); - } else { - PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTableUnsafeFreqs)->freq); - } - } else { - PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); + Result GpuVmin(u32 *ptr) + { + if (!C.marikoGpuVmin) + R_SKIP(); + PATCH_OFFSET(ptr, (int)C.marikoGpuVmin); + R_SUCCEED(); } - R_SUCCEED(); -} - -Result CpuVoltRange(u32* ptr) { - u32 min_volt_got = *(ptr - 1); - for (const auto& mv : CpuMinVolts) { - if (min_volt_got != mv) - continue; - - if (!C.marikoCpuMaxVolt) + Result GpuVmax(u32 *ptr) + { + if (!C.marikoGpuVmax) R_SKIP(); + PATCH_OFFSET(ptr, (int)C.marikoGpuVmax); + R_SUCCEED(); + } - PATCH_OFFSET(ptr, C.marikoCpuMaxVolt); - // Patch vmin for slt - if (C.marikoCpuUV) { - if (*(ptr-5) == 620) { - PATCH_OFFSET((ptr-5), C.marikoCpuVmin); - } - if (*(ptr-1) == 620) { - PATCH_OFFSET((ptr-1), 600); - } + Result CpuFreqVdd(u32 *ptr) + { + dvfs_rail *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(dvfs_rail, freq)); + R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry()); + R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry()); + if (C.enableMarikoCpuUnsafeFreqs) + { + PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); + } + else + { + if (C.marikoCpuUV) + { + if (!C.enableMarikoCpuUnsafeFreqs) + { + PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq); + } + else + { + PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTableUnsafeFreqs)->freq); + } + } } R_SUCCEED(); } - R_THROW(ldr::ResultInvalidCpuMinVolt()); -} -Result CpuVoltDfll(u32* ptr) { - cvb_cpu_dfll_data *entry = reinterpret_cast(ptr); + Result CpuVoltRange(u32 *ptr) + { + u32 min_volt_got = *(ptr - 1); + for (const auto &mv : CpuMinVolts) + { + if (min_volt_got != mv) + continue; - R_UNLESS(entry->tune0_low == 0x0000FFCF, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune0_high == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune1_low == 0x012207FF, ldr::ResultInvalidCpuVoltDfllEntry()); - R_UNLESS(entry->tune1_high == 0x03FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry()); - switch(C.marikoCpuUV) { - case 0: - break; - case 1: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FF88); //process_id 0 // EOS UV1 - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x00000000); - break; - case 2: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FF90); /// EOS Uv2 - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x00000000); - break; - case 3: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FF98); // EOS UV3 - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x00000000); - break; - case 4: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FFA0); // EOS Uv4 - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x00000000); - break; - case 5: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV5 - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x022217FF); - break; - case 6: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6 - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x024417FF); - break; - case 7: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6 - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x026617FF); - break; - case 8: - PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6 - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x028817FF); - break; - default: - break; + if (!C.marikoCpuMaxVolt) + R_SKIP(); + + PATCH_OFFSET(ptr, C.marikoCpuMaxVolt); + // Patch vmin for slt + if (C.marikoCpuUV) + { + if (*(ptr - 5) == 620) + { + PATCH_OFFSET((ptr - 5), C.marikoCpuVmin); + } + if (*(ptr - 1) == 620) + { + PATCH_OFFSET((ptr - 1), 600); + } + } + R_SUCCEED(); + } + R_THROW(ldr::ResultInvalidCpuMinVolt()); } - R_SUCCEED(); -} -Result GpuFreqMaxAsm(u32* ptr32) { - // Check if both two instructions match the pattern - u32 ins1 = *ptr32, ins2 = *(ptr32 + 1); - if (!(asm_compare_no_rd(ins1, asm_pattern[0]) && asm_compare_no_rd(ins2, asm_pattern[1]))) - R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + Result CpuVoltDfll(u32 *ptr) + { + cvb_cpu_dfll_data *entry = reinterpret_cast(ptr); - // Both instructions should operate on the same register - u8 rd = asm_get_rd(ins1); - if (rd != asm_get_rd(ins2)) - R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + R_UNLESS(entry->tune0_low == 0x0000FFCF, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune0_high == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune1_low == 0x012207FF, ldr::ResultInvalidCpuVoltDfllEntry()); + R_UNLESS(entry->tune1_high == 0x03FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry()); + switch (C.marikoCpuUV) + { + case 0: + break; + case 1: + PATCH_OFFSET(&(entry->tune0_low), 0x0000FF88); // process_id 0 // EOS UV1 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x00000000); + break; + case 2: + PATCH_OFFSET(&(entry->tune0_low), 0x0000FF90); /// EOS Uv2 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x00000000); + break; + case 3: + PATCH_OFFSET(&(entry->tune0_low), 0x0000FF98); // EOS UV3 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x00000000); + break; + case 4: + PATCH_OFFSET(&(entry->tune0_low), 0x0000FFA0); // EOS Uv4 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x00000000); + break; + case 5: + PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV5 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x022217FF); + break; + case 6: + PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x024417FF); + break; + case 7: + PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x026617FF); + break; + case 8: + PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x028817FF); + break; + default: + break; + } + R_SUCCEED(); + } - u32 max_clock; - switch(C.marikoGpuUV) { + Result GpuFreqMaxAsm(u32 *ptr32) + { + // Check if both two instructions match the pattern + u32 ins1 = *ptr32, ins2 = *(ptr32 + 1); + if (!(asm_compare_no_rd(ins1, asm_pattern[0]) && asm_compare_no_rd(ins2, asm_pattern[1]))) + R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + + // Both instructions should operate on the same register + u8 rd = asm_get_rd(ins1); + if (rd != asm_get_rd(ins2)) + R_THROW(ldr::ResultInvalidGpuFreqMaxPattern()); + + u32 max_clock; + switch (C.marikoGpuUV) + { case 0: max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq; break; @@ -172,7 +189,7 @@ Result GpuFreqMaxAsm(u32* ptr32) { max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq; break; case 3: - if(C.enableMarikoGpuUnsafeFreqs) + if (C.enableMarikoGpuUnsafeFreqs) { max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableUv3UnsafeFreqs)->freq; } @@ -184,341 +201,388 @@ Result GpuFreqMaxAsm(u32* ptr32) { default: max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq; break; - } - u32 asm_patch[2] = { - asm_set_rd(asm_set_imm16(asm_pattern[0], max_clock), rd), - asm_set_rd(asm_set_imm16(asm_pattern[1], max_clock >> 16), rd) - }; - PATCH_OFFSET(ptr32, asm_patch[0]); - PATCH_OFFSET(ptr32 + 1, asm_patch[1]); + } + u32 asm_patch[2] = { + asm_set_rd(asm_set_imm16(asm_pattern[0], max_clock), rd), + asm_set_rd(asm_set_imm16(asm_pattern[1], max_clock >> 16), rd)}; + PATCH_OFFSET(ptr32, asm_patch[0]); + PATCH_OFFSET(ptr32 + 1, asm_patch[1]); - R_SUCCEED(); -} - -Result GpuFreqPllLimit(u32* ptr) { - clk_pll_param* entry = reinterpret_cast(ptr); - - // All zero except for freq - for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) { - R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry()); + R_SUCCEED(); } - // Double the max clk simply - u32 max_clk = entry->freq * 2; - entry->freq = max_clk; - R_SUCCEED(); -} + Result GpuFreqPllLimit(u32 *ptr) + { + clk_pll_param *entry = reinterpret_cast(ptr); -/* Get RAM vendor data, ty b0rd2death! */ -/* Note: I know this is horrible but I don't care atm. */ -bool IsMicron() { - u64 packed_version; - splGetConfig((SplConfigItem)2, &packed_version); + // All zero except for freq + for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) + { + R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry()); + } - switch (packed_version) { - case 11: case 15: - case 25: case 26: case 27: - case 32: case 33: case 34: - /* RAM is Micron. */ + // Double the max clk simply + u32 max_clk = entry->freq * 2; + entry->freq = max_clk; + R_SUCCEED(); + } + + /* Get RAM vendor data, ty b0rd2death! */ + /* Note: I know this is horrible but I don't care atm. */ + bool IsMicron() + { + u64 packed_version; + splGetConfig((SplConfigItem)2, &packed_version); + + switch (packed_version) + { + case 11: + case 15: + case 25: + case 26: + case 27: + case 32: + case 33: + case 34: + /* RAM is Micron. */ return true; default: - /* Not Micron. */ + /* Not Micron. */ return false; - } -} - -void MemMtcTableAutoAdjust(MarikoMtcTable* table) { - /* Official Tegra X1 TRM, sign up for nvidia developer program (free) to download: - * https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual - * Section 18.11: MC Registers - * - * Retail Mariko: 200FBGA 16Gb DDP LPDDR4X SDRAM x 2 - * x16/Ch, 1Ch/die, Double-die, 2Ch, 1CS(rank), 8Gb density per die - * 64Mb x 16DQ x 8banks x 2channels = 2048MB (x32DQ) per package - * - * Devkit Mariko: 200FBGA 32Gb DDP LPDDR4X SDRAM x 2 - * x16/Ch, 1Ch/die, Quad-die, 2Ch, 2CS(rank), 8Gb density per die - * X1+ EMC can R/W to both ranks at the same time, resulting in doubled DQ - * 64Mb x 32DQ x 8banks x 2channels = 4096MB (x64DQ) per package - * - * If you have access to LPDDR4(X) specs or datasheets (from manufacturers or Google), - * you'd better calculate timings yourself rather than relying on following algorithm. - */ - - if (C.mtcConf != AUTO_ADJ) { - return; - } - - #define WRITE_PARAM_BURST_REG(TABLE, PARAM, VALUE) TABLE->burst_regs.PARAM = VALUE; - #define WRITE_PARAM_CA_TRAIN_REG(TABLE, PARAM, VALUE) TABLE->shadow_regs_ca_train.PARAM = VALUE; - #define WRITE_PARAM_RDWR_TRAIN_REG(TABLE, PARAM, VALUE) TABLE->shadow_regs_rdwr_train.PARAM = VALUE; - - #define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \ - WRITE_PARAM_BURST_REG(TABLE, PARAM, VALUE) \ - WRITE_PARAM_CA_TRAIN_REG(TABLE, PARAM, VALUE) \ - WRITE_PARAM_RDWR_TRAIN_REG(TABLE, PARAM, VALUE) - - #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg)) - - WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); - WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab)); - WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb)); - WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(tRAS)); - WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb)); - WRITE_PARAM_ALL_REG(table, emc_r2p, GET_CYCLE_CEIL(tRTP)); - WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD)); - WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD)); - WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); - WRITE_PARAM_ALL_REG(table, emc_refresh, REFRESH); - WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, REFRESH / 4); - - WRITE_PARAM_ALL_REG(table, emc_r2w, R2W); - WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); - WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); - - /* May or may not have to be patched in Micron; let's skip for now. */ - if (!IsMicron()) { - WRITE_PARAM_ALL_REG(table, emc_pdex2wr, GET_CYCLE_CEIL(tXP)); - WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(tXP)); - } - - WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(tXSR), (u32)0x3fe)); - WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(tXSR), (u32)0x3fe)); - WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR)); - WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); - WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(tRPab)); - WRITE_PARAM_ALL_REG(table, emc_trefbw, REFBW); - - /* Worth replacing with l4t dumps at some point. */ - // Burst MC Regs - #define WRITE_PARAM_BURST_MC_REG(TABLE, PARAM, VALUE) TABLE->burst_mc_regs.PARAM = VALUE; - - constexpr u32 MC_ARB_DIV = 4; - constexpr u32 MC_ARB_SFA = 2; - -// WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_cfg, C.marikoEmcMaxClock / (33.3 * 1000) / MC_ARB_DIV); //CYCLES_PER_UPDATE: The number of mcclk cycles per deadline timer update - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rcd, CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rp, CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rc, CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_ras, CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_faw, CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rrd, CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1) -// WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rap2pre, CEIL(GET_CYCLE_CEIL(tRTP) / MC_ARB_DIV)) -// WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_wap2pre, CEIL((WTP) / MC_ARB_DIV)) -// WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2r, CEIL(table->burst_regs.emc_rext / MC_ARB_DIV) - 1 + MC_ARB_SFA) -// WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2w, CEIL((R2W) / MC_ARB_DIV) - 1 + MC_ARB_SFA) -// WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_w2r, CEIL((W2R) / MC_ARB_DIV) - 1 + MC_ARB_SFA) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rfcpb, CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV)) -} - -void MemMtcPllmbDivisor(MarikoMtcTable* table) { - // Calculate DIVM and DIVN (clock divisors) - // Common PLL oscillator is 38.4 MHz - // PLLMB_OUT = 38.4 MHz / PLLLMB_DIVM * PLLMB_DIVN - typedef struct { - u8 numerator : 4; - u8 denominator : 4; - } pllmb_div; - - constexpr pllmb_div div[] = { - {3, 4}, {2, 3}, {1, 2}, {1, 3}, {1, 4}, {0, 2} - }; - - constexpr u32 pll_osc_in = 38'400; - u32 divm {}, divn {}; - const u32 remainder = C.marikoEmcMaxClock % pll_osc_in; - for (const auto &index : div) { - // Round down - if (remainder >= pll_osc_in * index.numerator / index.denominator) { - divm = index.denominator; - divn = C.marikoEmcMaxClock / pll_osc_in * divm + index.numerator; - break; } } - table->pllmb_divm = divm; - table->pllmb_divn = divn; -} + void MemMtcTableAutoAdjust(MarikoMtcTable *table) + { + /* Official Tegra X1 TRM, sign up for nvidia developer program (free) to download: + * https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual + * Section 18.11: MC Registers + * + * Retail Mariko: 200FBGA 16Gb DDP LPDDR4X SDRAM x 2 + * x16/Ch, 1Ch/die, Double-die, 2Ch, 1CS(rank), 8Gb density per die + * 64Mb x 16DQ x 8banks x 2channels = 2048MB (x32DQ) per package + * + * Devkit Mariko: 200FBGA 32Gb DDP LPDDR4X SDRAM x 2 + * x16/Ch, 1Ch/die, Quad-die, 2Ch, 2CS(rank), 8Gb density per die + * X1+ EMC can R/W to both ranks at the same time, resulting in doubled DQ + * 64Mb x 32DQ x 8banks x 2channels = 4096MB (x64DQ) per package + * + * If you have access to LPDDR4(X) specs or datasheets (from manufacturers or Google), + * you'd better calculate timings yourself rather than relying on following algorithm. + */ -Result MemFreqMtcTable(u32* ptr) { - u32 khz_list[] = { 1600000, 1331200, 204000 }; - u32 khz_list_size = sizeof(khz_list) / sizeof(u32); + if (C.mtcConf != AUTO_ADJ) + { + return; + } - // Generate list for mtc table pointers - MarikoMtcTable* table_list[khz_list_size]; - for (u32 i = 0; i < khz_list_size; i++) { - u8* table = reinterpret_cast(ptr) - offsetof(MarikoMtcTable, rate_khz) - i * sizeof(MarikoMtcTable); - table_list[i] = reinterpret_cast(table); - R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable()); - R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); +#define WRITE_PARAM_BURST_REG(TABLE, PARAM, VALUE) TABLE->burst_regs.PARAM = VALUE; +#define WRITE_PARAM_CA_TRAIN_REG(TABLE, PARAM, VALUE) TABLE->shadow_regs_ca_train.PARAM = VALUE; +#define WRITE_PARAM_RDWR_TRAIN_REG(TABLE, PARAM, VALUE) TABLE->shadow_regs_rdwr_train.PARAM = VALUE; + +#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \ + WRITE_PARAM_BURST_REG(TABLE, PARAM, VALUE) \ + WRITE_PARAM_CA_TRAIN_REG(TABLE, PARAM, VALUE) \ + WRITE_PARAM_RDWR_TRAIN_REG(TABLE, PARAM, VALUE) + +#define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg)) + + WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC)); + WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab)); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb)); + WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(tRAS)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb)); + WRITE_PARAM_ALL_REG(table, emc_r2p, GET_CYCLE_CEIL(tRTP)); + WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD)); + WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD)); + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD)); + WRITE_PARAM_ALL_REG(table, emc_refresh, REFRESH); + WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, REFRESH / 4); + + WRITE_PARAM_ALL_REG(table, emc_r2w, R2W); + WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); + WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); + + /* May or may not have to be patched in Micron; let's skip for now. */ + if (!IsMicron()) + { + WRITE_PARAM_ALL_REG(table, emc_pdex2wr, GET_CYCLE_CEIL(tXP)); + WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(tXP)); + } + + WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(tXSR), (u32)0x3fe)); + WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(tXSR), (u32)0x3fe)); + WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR)); + WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW)); + WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(tRPab)); + WRITE_PARAM_ALL_REG(table, emc_trefbw, REFBW); + +/* Worth replacing with l4t dumps at some point. */ +// Burst MC Regs +#define WRITE_PARAM_BURST_MC_REG(TABLE, PARAM, VALUE) TABLE->burst_mc_regs.PARAM = VALUE; + + constexpr u32 MC_ARB_DIV = 4; + constexpr u32 MC_ARB_SFA = 2; + + // WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_cfg, C.marikoEmcMaxClock / (33.3 * 1000) / MC_ARB_DIV); //CYCLES_PER_UPDATE: The number of mcclk cycles per deadline timer update + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rcd, CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rp, CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rc, CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_ras, CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_faw, CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rrd, CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1) + // WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rap2pre, CEIL(GET_CYCLE_CEIL(tRTP) / MC_ARB_DIV)) + // WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_wap2pre, CEIL((WTP) / MC_ARB_DIV)) + // WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2r, CEIL(table->burst_regs.emc_rext / MC_ARB_DIV) - 1 + MC_ARB_SFA) + // WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2w, CEIL((R2W) / MC_ARB_DIV) - 1 + MC_ARB_SFA) + // WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_w2r, CEIL((W2R) / MC_ARB_DIV) - 1 + MC_ARB_SFA) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rfcpb, CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV)) } - if (C.marikoEmcMaxClock <= EmcClkOSLimit) - R_SKIP(); + void MemMtcPllmbDivisor(MarikoMtcTable *table) + { + // Calculate DIVM and DIVN (clock divisors) + // Common PLL oscillator is 38.4 MHz + // PLLMB_OUT = 38.4 MHz / PLLLMB_DIVM * PLLMB_DIVN + typedef struct + { + u8 numerator : 4; + u8 denominator : 4; + } pllmb_div; - MarikoMtcTable *table_alt = table_list[1], *table_max = table_list[0]; - MarikoMtcTable *tmp = new MarikoMtcTable; + constexpr pllmb_div div[] = { + {3, 4}, {2, 3}, {1, 2}, {1, 3}, {1, 4}, {0, 2}}; - // Copy unmodified 1600000 table to tmp - std::memcpy(reinterpret_cast(tmp), reinterpret_cast(table_max), sizeof(MarikoMtcTable)); - // Adjust max freq mtc timing parameters with reference to 1331200 table - MemMtcTableAutoAdjust(table_max); - MemMtcPllmbDivisor(table_max); - // Overwrite 13312000 table with unmodified 1600000 table copied back - std::memcpy(reinterpret_cast(table_alt), reinterpret_cast(tmp), sizeof(MarikoMtcTable)); - - delete tmp; - - PATCH_OFFSET(ptr, C.marikoEmcMaxClock); - - // Handle customize table replacement - //if (C.mtcConf == CUSTOMIZED_ALL) { - // MemMtcCustomizeTable(table_list[0], reinterpret_cast(reinterpret_cast(C.marikoMtcTable))); - // } - - R_SUCCEED(); -} - -Result MemFreqDvbTable(u32* ptr) { - emc_dvb_dvfs_table_t* default_end = reinterpret_cast(ptr); - emc_dvb_dvfs_table_t* new_start = default_end + 1; - - // Validate existing table - void* mem_dvb_table_head = reinterpret_cast(new_start) - sizeof(EmcDvbTableDefault); - bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0; - R_UNLESS(validated, ldr::ResultInvalidDvbTable()); - - if (C.marikoEmcMaxClock <= EmcClkOSLimit) - R_SKIP(); - - int32_t voltAdd = 25*C.marikoEmcDvbShift; - - #define DVB_VOLT(zero, one, two) std::min(zero+voltAdd, 1050), std::min(one+voltAdd, 1025), std::min(two+voltAdd, 1000), - - if (C.marikoEmcMaxClock < 1862400) { - std::memcpy(new_start, default_end, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 2131200){ - emc_dvb_dvfs_table_t oc_table = { 1862400, { 700, 675, 650, } }; - std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 2400000){ - emc_dvb_dvfs_table_t oc_table = { 2131200, { 725, 700, 675, } }; - std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 2665600){ - emc_dvb_dvfs_table_t oc_table = { 2400000, { DVB_VOLT(750, 725, 700) } }; - std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 2931200){ - emc_dvb_dvfs_table_t oc_table = { 2665600, { DVB_VOLT(775, 750, 725) } }; - std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else if (C.marikoEmcMaxClock < 3200000){ - emc_dvb_dvfs_table_t oc_table = { 2931200, { DVB_VOLT(800, 775, 750) } }; - std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } else { - emc_dvb_dvfs_table_t oc_table = { 3200000, { DVB_VOLT(800, 800, 775) } }; - std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); - } - new_start->freq = C.marikoEmcMaxClock; - /* Max dvfs entry is 32, but HOS doesn't seem to boot if exact freq doesn't exist in dvb table, - reason why it's like this - */ - - R_SUCCEED(); -} - -Result MemFreqMax(u32* ptr) { - if (C.marikoEmcMaxClock <= EmcClkOSLimit) - R_SKIP(); - - PATCH_OFFSET(ptr, C.marikoEmcMaxClock); - R_SUCCEED(); -} - -Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) { - struct { - u8 reg; - u8 val; - } __attribute__((packed)) cmd; - - I2cSession _session; - Result res = i2cOpenSession(&_session, dev); - if (R_FAILED(res)) - return res; - - cmd.reg = reg; - cmd.val = val; - res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); - i2csessionClose(&_session); - return res; -} - -Result EmcVddqVolt(u32* ptr) { - regulator* entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_2_3.default_uv)); - - constexpr u32 uv_step = 5'000; - constexpr u32 uv_min = 250'000; - - auto validator = [entry]() { - R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry()); - R_SUCCEED(); - }; - - R_TRY(validator()); - - u32 emc_uv = C.marikoEmcVddqVolt; - if (!emc_uv) - R_SKIP(); - - if (emc_uv % uv_step) - emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding - - PATCH_OFFSET(ptr, emc_uv); - - i2cInitialize(); - I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step); - i2cExit(); - - R_SUCCEED(); -} - -void Patch(uintptr_t mapped_nso, size_t nso_size) { - u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq); - u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq); - - PatcherEntry patches[] = { - { "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit }, - { "CPU Freq Table", CpuFreqCvbTable, 1, nullptr, CpuCvbDefaultMaxFreq }, - { "CPU Volt Limit", &CpuVoltRange, 13, nullptr, CpuVoltOfficial }, - { "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, 0x0000FFCF }, - { "GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq }, - { "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn }, - { "GPU Freq PLL", &GpuFreqPllLimit, 1, nullptr, GpuClkPllLimit }, - { "MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, EmcClkOSLimit }, - { "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit }, - { "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit }, - { "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit }, - { "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault }, - { "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default }, - { "GPU Vmin", &GpuVmin, 0, nullptr, gpuVmin}, - { "GPU Vmax", &GpuVmax, 0, nullptr, gpuVmax}, - }; - - for (uintptr_t ptr = mapped_nso; - ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); - ptr += sizeof(u32)) { - u32* ptr32 = reinterpret_cast(ptr); - for (auto& entry : patches) { - if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) + constexpr u32 pll_osc_in = 38'400; + u32 divm{}, divn{}; + const u32 remainder = C.marikoEmcMaxClock % pll_osc_in; + for (const auto &index : div) + { + // Round down + if (remainder >= pll_osc_in * index.numerator / index.denominator) + { + divm = index.denominator; + divn = C.marikoEmcMaxClock / pll_osc_in * divm + index.numerator; break; + } + } + + table->pllmb_divm = divm; + table->pllmb_divn = divn; + } + + Result MemFreqMtcTable(u32 *ptr) + { + u32 khz_list[] = {1600000, 1331200, 204000}; + u32 khz_list_size = sizeof(khz_list) / sizeof(u32); + + // Generate list for mtc table pointers + MarikoMtcTable *table_list[khz_list_size]; + for (u32 i = 0; i < khz_list_size; i++) + { + u8 *table = reinterpret_cast(ptr) - offsetof(MarikoMtcTable, rate_khz) - i * sizeof(MarikoMtcTable); + table_list[i] = reinterpret_cast(table); + R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable()); + R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable()); + } + + if (C.marikoEmcMaxClock <= EmcClkOSLimit) + R_SKIP(); + + MarikoMtcTable *table_alt = table_list[1], *table_max = table_list[0]; + MarikoMtcTable *tmp = new MarikoMtcTable; + + // Copy unmodified 1600000 table to tmp + std::memcpy(reinterpret_cast(tmp), reinterpret_cast(table_max), sizeof(MarikoMtcTable)); + // Adjust max freq mtc timing parameters with reference to 1331200 table + MemMtcTableAutoAdjust(table_max); + MemMtcPllmbDivisor(table_max); + // Overwrite 13312000 table with unmodified 1600000 table copied back + std::memcpy(reinterpret_cast(table_alt), reinterpret_cast(tmp), sizeof(MarikoMtcTable)); + + delete tmp; + + PATCH_OFFSET(ptr, C.marikoEmcMaxClock); + + // Handle customize table replacement + // if (C.mtcConf == CUSTOMIZED_ALL) { + // MemMtcCustomizeTable(table_list[0], reinterpret_cast(reinterpret_cast(C.marikoMtcTable))); + // } + + R_SUCCEED(); + } + + Result MemFreqDvbTable(u32 *ptr) + { + emc_dvb_dvfs_table_t *default_end = reinterpret_cast(ptr); + emc_dvb_dvfs_table_t *new_start = default_end + 1; + + // Validate existing table + void *mem_dvb_table_head = reinterpret_cast(new_start) - sizeof(EmcDvbTableDefault); + bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0; + R_UNLESS(validated, ldr::ResultInvalidDvbTable()); + + if (C.marikoEmcMaxClock <= EmcClkOSLimit) + R_SKIP(); + + int32_t voltAdd = 25 * C.marikoEmcDvbShift; + +#define DVB_VOLT(zero, one, two) std::min(zero + voltAdd, 1050), std::min(one + voltAdd, 1025), std::min(two + voltAdd, 1000), + + if (C.marikoEmcMaxClock < 1862400) + { + std::memcpy(new_start, default_end, sizeof(emc_dvb_dvfs_table_t)); + } + else if (C.marikoEmcMaxClock < 2131200) + { + emc_dvb_dvfs_table_t oc_table = {1862400, { + 700, + 675, + 650, + }}; + std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + } + else if (C.marikoEmcMaxClock < 2400000) + { + emc_dvb_dvfs_table_t oc_table = {2131200, { + 725, + 700, + 675, + }}; + std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + } + else if (C.marikoEmcMaxClock < 2665600) + { + emc_dvb_dvfs_table_t oc_table = {2400000, {DVB_VOLT(750, 725, 700)}}; + std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + } + else if (C.marikoEmcMaxClock < 2931200) + { + emc_dvb_dvfs_table_t oc_table = {2665600, {DVB_VOLT(775, 750, 725)}}; + std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + } + else if (C.marikoEmcMaxClock < 3200000) + { + emc_dvb_dvfs_table_t oc_table = {2931200, {DVB_VOLT(800, 775, 750)}}; + std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + } + else + { + emc_dvb_dvfs_table_t oc_table = {3200000, {DVB_VOLT(800, 800, 775)}}; + std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t)); + } + new_start->freq = C.marikoEmcMaxClock; + /* Max dvfs entry is 32, but HOS doesn't seem to boot if exact freq doesn't exist in dvb table, + reason why it's like this + */ + + R_SUCCEED(); + } + + Result MemFreqMax(u32 *ptr) + { + if (C.marikoEmcMaxClock <= EmcClkOSLimit) + R_SKIP(); + + PATCH_OFFSET(ptr, C.marikoEmcMaxClock); + R_SUCCEED(); + } + + Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) + { + struct + { + u8 reg; + u8 val; + } __attribute__((packed)) cmd; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (R_FAILED(res)) + return res; + + cmd.reg = reg; + cmd.val = val; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + i2csessionClose(&_session); + return res; + } + + Result EmcVddqVolt(u32 *ptr) + { + regulator *entry = reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_2_3.default_uv)); + + constexpr u32 uv_step = 5'000; + constexpr u32 uv_min = 250'000; + + auto validator = [entry]() + { + R_UNLESS(entry->id == 2, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type == 3, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_2_3.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_2_3.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry()); + R_SUCCEED(); + }; + + R_TRY(validator()); + + u32 emc_uv = C.marikoEmcVddqVolt; + if (!emc_uv) + R_SKIP(); + + if (emc_uv % uv_step) + emc_uv = (emc_uv + uv_step - 1) / uv_step * uv_step; // rounding + + PATCH_OFFSET(ptr, emc_uv); + + i2cInitialize(); + I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step); + i2cExit(); + + R_SUCCEED(); + } + + void Patch(uintptr_t mapped_nso, size_t nso_size) + { + u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq); + u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq); + + PatcherEntry patches[] = { + {"CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit}, + {"CPU Freq Table", CpuFreqCvbTable, 1, nullptr, CpuCvbDefaultMaxFreq}, + {"CPU Volt Limit", &CpuVoltRange, 13, nullptr, CpuVoltOfficial}, + {"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, 0x0000FFCF}, + {"GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq}, + {"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn}, + {"GPU Freq PLL", &GpuFreqPllLimit, 1, nullptr, GpuClkPllLimit}, + {"MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, EmcClkOSLimit}, + {"MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit}, + {"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit}, + {"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit}, + {"MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault}, + {"MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default}, + {"GPU Vmin", &GpuVmin, 0, nullptr, gpuVmin}, + {"GPU Vmax", &GpuVmax, 0, nullptr, gpuVmax}, + }; + + for (uintptr_t ptr = mapped_nso; + ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); + ptr += sizeof(u32)) + { + u32 *ptr32 = reinterpret_cast(ptr); + for (auto &entry : patches) + { + if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) + break; + } + } + + for (auto &entry : patches) + { + LOGGING("%s Count: %zu", entry.description, entry.patched_count); + if (R_FAILED(entry.CheckResult())) + CRASH(entry.description); } } - for (auto& entry : patches) { - LOGGING("%s Count: %zu", entry.description, entry.patched_count); - if (R_FAILED(entry.CheckResult())) - CRASH(entry.description); - } -} - }