diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp index c79a247f..5b960cdd 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp @@ -1,8 +1,6 @@ /* * Copyright (C) Switch-OC-Suite * - * Copyright (c) 2023 hanai3Bi - * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. @@ -16,131 +14,131 @@ * along with this program. If not, see . */ -#include "pcv.hpp" + #include "pcv.hpp" -namespace ams::ldr::oc::pcv { - -Result MemFreqPllmLimit(u32* ptr) { - clk_pll_param* entry = reinterpret_cast(ptr); - R_UNLESS(entry->freq == entry->vco_max, ldr::ResultInvalidMemPllmEntry()); - - // Double the max clk simply - u32 max_clk = entry->freq * 2; - entry->freq = max_clk; - entry->vco_max = max_clk; - R_SUCCEED(); -} - -Result MemVoltHandler(u32* ptr) { - // ptr value might be default_uv or max_uv - regulator* entries[2] = { - reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_1.default_uv)), - reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_1.max_uv)), - }; - - constexpr u32 uv_step = 12'500; - constexpr u32 uv_min = 600'000; - - auto validator = [](regulator* entry) { - R_UNLESS(entry->id == 1, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type == 1, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_1.volt_reg == 0x17, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_1.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry()); - R_UNLESS(entry->type_1.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry()); - R_SUCCEED(); - }; - - regulator* entry = nullptr; - for (auto& i : entries) { - if (R_SUCCEEDED(validator(i))) - entry = i; - } - - R_UNLESS(entry, ldr::ResultInvalidRegulatorEntry()); - - u32 emc_uv = C.commonEmcMemVolt; - if (!emc_uv) - R_SKIP(); - - if (emc_uv % uv_step) - emc_uv = emc_uv / uv_step * uv_step; // rounding - - PATCH_OFFSET(ptr, emc_uv); - - R_SUCCEED(); -} - -void SafetyCheck() { - if (C.custRev != CUST_REV) - CRASH("Triggered"); - - struct sValidator { - volatile u32 value; - u32 min; - u32 max; - bool value_required = false; - - Result check() { - if (!value_required && !value) - R_SUCCEED(); - - if (min && value < min) - R_THROW(ldr::ResultSafetyCheckFailure()); - if (max && value > max) - R_THROW(ldr::ResultSafetyCheckFailure()); - - R_SUCCEED(); - } - }; - - u32 eristaCpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.eristaCpuDvfsTable)->freq); - u32 marikoCpuDvfsMaxFreq = static_cast(C.marikoCpuUV ? GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq : GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); - u32 eristaGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq); - u32 marikoGpuDvfsMaxFreq; - switch (C.marikoGpuUV) { - case 0: - marikoGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq); - break; - case 1: - marikoGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq); - break; - case 2: - marikoGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq); - break; - default: - marikoGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq); - break; - } - - sValidator validators[] = { - { C.commonCpuBoostClock, 1020'000, 3000'000, true }, - { C.commonEmcMemVolt, 1100'000, 1250'000 }, - { C.eristaCpuMaxVolt, 1100, 1300 }, - { C.eristaEmcMaxClock, 1600'000, 2131'200 }, - { C.marikoCpuMaxVolt, 1100, 1300 }, - { C.marikoEmcMaxClock, 1600'000, 2800'000 }, - { C.marikoEmcVddqVolt, 550'000, 650'000 }, - { eristaCpuDvfsMaxFreq, 1785'000, 3000'000 }, - { marikoCpuDvfsMaxFreq, 1785'000, 3000'000 }, - { eristaGpuDvfsMaxFreq, 768'000, 1536'000 }, - { marikoGpuDvfsMaxFreq, 768'000, 1536'000 }, - }; - - for (auto& i : validators) { - if (R_FAILED(i.check())) - CRASH("Triggered"); - } -} - -void Patch(uintptr_t mapped_nso, size_t nso_size) { - #ifdef ATMOSPHERE_IS_STRATOSPHERE - SafetyCheck(); - bool isMariko = (spl::GetSocType() == spl::SocType_Mariko); - if (isMariko) - mariko::Patch(mapped_nso, nso_size); - else - erista::Patch(mapped_nso, nso_size); - #endif -} - -} \ No newline at end of file + namespace ams::ldr::oc::pcv { + + Result MemFreqPllmLimit(u32* ptr) { + clk_pll_param* entry = reinterpret_cast(ptr); + R_UNLESS(entry->freq == entry->vco_max, ldr::ResultInvalidMemPllmEntry()); + + // Double the max clk simply + u32 max_clk = entry->freq * 2; + entry->freq = max_clk; + entry->vco_max = max_clk; + R_SUCCEED(); + } + + Result MemVoltHandler(u32* ptr) { + // ptr value might be default_uv or max_uv + regulator* entries[2] = { + reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_1.default_uv)), + reinterpret_cast(reinterpret_cast(ptr) - offsetof(regulator, type_1.max_uv)), + }; + + constexpr u32 uv_step = 12'500; + constexpr u32 uv_min = 600'000; + + auto validator = [](regulator* entry) { + R_UNLESS(entry->id == 1, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type == 1, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_1.volt_reg == 0x17, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_1.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry()); + R_UNLESS(entry->type_1.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry()); + R_SUCCEED(); + }; + + regulator* entry = nullptr; + for (auto& i : entries) { + if (R_SUCCEEDED(validator(i))) + entry = i; + } + + R_UNLESS(entry, ldr::ResultInvalidRegulatorEntry()); + + u32 emc_uv = C.commonEmcMemVolt; + if (!emc_uv) + R_SKIP(); + + if (emc_uv % uv_step) + emc_uv = emc_uv / uv_step * uv_step; // rounding + + PATCH_OFFSET(ptr, emc_uv); + + R_SUCCEED(); + } + + void SafetyCheck() { + if (C.custRev != CUST_REV) + CRASH("Triggered"); + + struct sValidator { + volatile u32 value; + u32 min; + u32 max; + bool value_required = false; + + Result check() { + if (!value_required && !value) + R_SUCCEED(); + + if (min && value < min) + R_THROW(ldr::ResultSafetyCheckFailure()); + if (max && value > max) + R_THROW(ldr::ResultSafetyCheckFailure()); + + R_SUCCEED(); + } + }; + + u32 eristaCpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.eristaCpuDvfsTable)->freq); + u32 marikoCpuDvfsMaxFreq = static_cast(C.marikoCpuUV ? GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq : GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); + u32 eristaGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq); + u32 marikoGpuDvfsMaxFreq; + switch (C.marikoGpuUV) { + case 0: + marikoGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq); + break; + case 1: + marikoGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq); + break; + case 2: + marikoGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq); + break; + default: + marikoGpuDvfsMaxFreq = static_cast(GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq); + break; + } + + sValidator validators[] = { + { C.commonCpuBoostClock, 1020'000, 3000'000, true }, + { C.commonEmcMemVolt, 1000'000, 1350'000 }, + { C.eristaCpuMaxVolt, 1100, 1300 }, + { C.eristaEmcMaxClock, 1600'000, 2131'200 }, + { C.marikoCpuMaxVolt, 800, 1160 }, + { C.marikoEmcMaxClock, 1600'000, 3600'000 }, + { C.marikoEmcVddqVolt, 550'000, 650'000 }, + { eristaCpuDvfsMaxFreq, 1785'000, 3000'000 }, + { marikoCpuDvfsMaxFreq, 1785'000, 3000'000 }, + { eristaGpuDvfsMaxFreq, 768'000, 1536'000 }, + { marikoGpuDvfsMaxFreq, 768'000, 1536'000 }, + }; + + for (auto& i : validators) { + if (R_FAILED(i.check())) + CRASH("Triggered"); + } + } + + void Patch(uintptr_t mapped_nso, size_t nso_size) { + #ifdef ATMOSPHERE_IS_STRATOSPHERE + SafetyCheck(); + bool isMariko = (spl::GetSocType() == spl::SocType_Mariko); + if (isMariko) + mariko::Patch(mapped_nso, nso_size); + else + erista::Patch(mapped_nso, nso_size); + #endif + } + + } \ No newline at end of file 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 47630a5c..9c131a32 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp @@ -1,8 +1,6 @@ /* * Copyright (C) Switch-OC-Suite * - * Copyright (c) 2023 hanai3Bi - * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. @@ -16,323 +14,326 @@ * along with this program. If not, see . */ -#include "pcv.hpp" -#include "../mtc_timing_value.hpp" -#include "../customize.hpp" - -namespace ams::ldr::oc::pcv::mariko { - -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) { - PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq); - } else { - PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); - } + #include "pcv.hpp" + #include "../mtc_timing_value.hpp" + + namespace ams::ldr::oc::pcv::mariko { + + 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) { + PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq); + } else { + PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq); + } + + 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) + R_SKIP(); + + PATCH_OFFSET(ptr, C.marikoCpuMaxVolt); + // Patch vmin for slt + if (C.marikoCpuUV) { + if (*(ptr-5) == 620) { + PATCH_OFFSET((ptr-5), 600); + } + if (*(ptr-1) == 620) { + PATCH_OFFSET((ptr-1), 600); + } + + } + R_SUCCEED(); + } + R_THROW(ldr::ResultInvalidCpuMinVolt()); + } + + Result CpuVoltDfll(u32* ptr) { + cvb_cpu_dfll_data *entry = reinterpret_cast(ptr); + + 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()); + + if (C.marikoCpuUV) { + if (C.marikoCpuUV == 1) { + PATCH_OFFSET(&(entry->tune0_low), 0x0000FF90); //process_id 0 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x00000000); + } + else if (C.marikoCpuUV == 2) { + PATCH_OFFSET(&(entry->tune0_low), 0x0000FFA0); //process_id 1 + PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); + PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); + PATCH_OFFSET(&(entry->tune1_high), 0x00000000); + } + } + + 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()); + + // 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; + case 1: + max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq; + break; + case 2: + max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq; + break; + 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]); + + 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()); + } + + // Double the max clk simply + u32 max_clk = entry->freq * 2; + entry->freq = max_clk; + R_SUCCEED(); + } + + void MemMtcTableAutoAdjust(MarikoMtcTable* table, const MarikoMtcTable* ref) { + /* 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_ALL) + return; + + // scale with linear interpolation + #define ADJUST_PROP(TARGET, REF) \ + (u32)(CEIL((REF + ((C.marikoEmcMaxClock-EmcClkOSAlt)*(TARGET-REF))/(EmcClkOSLimit-EmcClkOSAlt)))) + + #define ADJUST_PARAM(TARGET, REF) \ + TARGET = ADJUST_PROP(TARGET, REF); + + #define ADJUST_PARAM_TABLE(TABLE, PARAM, REF) ADJUST_PARAM(TABLE->PARAM, REF->PARAM) + + // Burst Register + #define ADJUST_PARAM_ALL_REG(TABLE, PARAM, REF) \ + ADJUST_PARAM_TABLE(TABLE, burst_regs.PARAM, REF) \ + ADJUST_PARAM_TABLE(TABLE, shadow_regs_ca_train.PARAM, REF) \ + ADJUST_PARAM_TABLE(TABLE, shadow_regs_rdwr_train.PARAM, REF) + + #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)) - 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) - R_SKIP(); - - PATCH_OFFSET(ptr, C.marikoCpuMaxVolt); - // Patch vmin for slt - if (C.marikoCpuUV) { - if (*(ptr-5) == 620) { - PATCH_OFFSET((ptr-5), 600); - } - if (*(ptr-1) == 620) { - PATCH_OFFSET((ptr-1), 600); - } - - } - R_SUCCEED(); - } - R_THROW(ldr::ResultInvalidCpuMinVolt()); -} - -Result CpuVoltDfll(u32* ptr) { - cvb_cpu_dfll_data *entry = reinterpret_cast(ptr); - - 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()); - - if (C.marikoCpuUV) { - if (C.marikoCpuUV == 1) { - PATCH_OFFSET(&(entry->tune0_low), 0x0000FF90); //process_id 0 - } else if (C.marikoCpuUV == 2) { - PATCH_OFFSET(&(entry->tune0_low), 0x0000FFA0); //process_id 1 - } - PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF); - PATCH_OFFSET(&(entry->tune1_low), 0x021107FF); - PATCH_OFFSET(&(entry->tune1_high), 0x00000000); - } - - 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()); - - // 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; - case 1: - max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq; - break; - case 2: - max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq; - break; - 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]); - - 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()); - } - - // Double the max clk simply - u32 max_clk = entry->freq * 2; - entry->freq = max_clk; - R_SUCCEED(); -} - -void MemMtcTableAutoAdjust(MarikoMtcTable* table, const MarikoMtcTable* ref) { - /* 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_ALL) - return; - - // scale with linear interpolation - #define ADJUST_PROP(TARGET, REF) \ - (u32)(CEIL((REF + ((C.marikoEmcMaxClock-EmcClkOSAlt)*(TARGET-REF))/(EmcClkOSLimit-EmcClkOSAlt)))) - - #define ADJUST_PARAM(TARGET, REF) \ - TARGET = ADJUST_PROP(TARGET, REF); - - #define ADJUST_PARAM_TABLE(TABLE, PARAM, REF) ADJUST_PARAM(TABLE->PARAM, REF->PARAM) - - // Burst Register - #define ADJUST_PARAM_ALL_REG(TABLE, PARAM, REF) \ - ADJUST_PARAM_TABLE(TABLE, burst_regs.PARAM, REF) \ - ADJUST_PARAM_TABLE(TABLE, shadow_regs_ca_train.PARAM, REF) \ - ADJUST_PARAM_TABLE(TABLE, shadow_regs_rdwr_train.PARAM, REF) - - #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(C.tRC)); - WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(C.tRFCab)); - WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(C.tRFCpb)); - WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(C.tRAS)); - WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(C.tRPpb)); - WRITE_PARAM_ALL_REG(table, emc_r2w, R2W); - WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); - WRITE_PARAM_ALL_REG(table, emc_r2p, GET_CYCLE_CEIL(C.tRTP)); - WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); - WRITE_PARAM_ALL_REG(table, emc_trtm, RTM); - WRITE_PARAM_ALL_REG(table, emc_twtm, WTM); - WRITE_PARAM_ALL_REG(table, emc_tratm, RATM); - WRITE_PARAM_ALL_REG(table, emc_twatm, WATM); - //WRITE_PARAM_ALL_REG(table, emc_tr2ref, GET_CYCLE_CEIL(C.tR2REF)); - WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(C.tRCD)); - WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(C.tRCD)); - WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(C.tRRD)); - WRITE_PARAM_ALL_REG(table, emc_rext, 26); - 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_pdex2wr, GET_CYCLE_CEIL(C.tXP)); - WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(C.tXP)); - WRITE_PARAM_ALL_REG(table, emc_pchg2pden, GET_CYCLE_CEIL(C.tCMDCKE)); - WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(C.tMRWCKEL)); - WRITE_PARAM_ALL_REG(table, emc_ar2pden, GET_CYCLE_CEIL(C.tCMDCKE)); - WRITE_PARAM_ALL_REG(table, emc_rw2pden, WTPDEN); - WRITE_PARAM_ALL_REG(table, emc_cke2pden, GET_CYCLE_CEIL(C.tCKELCS)); - //WRITE_PARAM_ALL_REG(table, emc_pdex2cke, GET_CYCLE_CEIL(C.tCSCKEH)); - WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(tPDEX2MRR)); - WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(C.tXSR), (u32)0x3fe)); - WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(C.tXSR), (u32)0x3fe)); - WRITE_PARAM_ALL_REG(table, emc_tcke, GET_CYCLE_CEIL(C.tCKE) + 1); - WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(C.tSR)); - WRITE_PARAM_ALL_REG(table, emc_tpd, GET_CYCLE_CEIL(C.tCKE)); - WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(C.tFAW)); - WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(C.tRPab)); - //WRITE_PARAM_ALL_REG(table, emc_tclkstable, GET_CYCLE_CEIL(C.tCKCKEH)); - WRITE_PARAM_ALL_REG(table, emc_tclkstop, GET_CYCLE_CEIL(C.tCKE) + 8); - WRITE_PARAM_ALL_REG(table, emc_trefbw, REFBW); - - ADJUST_PARAM_ALL_REG(table, emc_dyn_self_ref_control, ref); - - - #define CLEAR_BIT(BITS, HIGH, LOW) \ - BITS = BITS & ~( ((1u << HIGH) << 1u) - (1u << LOW) ); - - #define ADJUST(TARGET) (u32)CEIL(TARGET * (C.marikoEmcMaxClock / EmcClkOSLimit)) - #define ADJUST_INVERSE(TARGET) (u32)(TARGET * (EmcClkOSLimit / 1000) / (C.marikoEmcMaxClock / 1000)) - - // 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(C.tRCD) / MC_ARB_DIV) - 2) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rp, CEIL(GET_CYCLE_CEIL(C.tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rc, CEIL(GET_CYCLE_CEIL(C.tRC) / MC_ARB_DIV) - 1) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_ras, CEIL(GET_CYCLE_CEIL(C.tRAS) / MC_ARB_DIV) - 2) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_faw, CEIL(GET_CYCLE_CEIL(C.tFAW) / MC_ARB_DIV) - 1) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rrd, CEIL(GET_CYCLE_CEIL(C.tRRD) / MC_ARB_DIV) - 1) - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rap2pre, CEIL(GET_CYCLE_CEIL(C.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(C.tRFCpb) / MC_ARB_DIV)) - - u32 DA_TURNS = 0; - DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; //R2W TURN - DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; //W2R TURN - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_turns, DA_TURNS); - u32 DA_COVERS = 0; - u8 R_COVER = (table->burst_mc_regs.mc_emem_arb_timing_rap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; - u8 W_COVER = (table->burst_mc_regs.mc_emem_arb_timing_wap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; - DA_COVERS |= (u8)(table->burst_mc_regs.mc_emem_arb_timing_rc / 2); //RC COVER - DA_COVERS |= (R_COVER << 8); //RCD_R COVER - DA_COVERS |= (W_COVER << 16); //RCD_W COVER - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_covers, DA_COVERS); - - CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 7, 0); - table->burst_mc_regs.mc_emem_arb_misc0 |= u8(table->burst_mc_regs.mc_emem_arb_timing_rc + 1); //BC2AA_HOLDOFF - CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 14, 8); - table->burst_mc_regs.mc_emem_arb_misc0 |= u8((ADJUST(0x24) << 8)); //PRIORITY_INVERSION_THRESHOLD - CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 20, 16); - table->burst_mc_regs.mc_emem_arb_misc0 |= u8((ADJUST(12) << 16)); //PRIORITY_INVERSION_ISO_THRESHOLD - - // updown registers - #define ADJUST_PARAM_LA_SCALE_REG(TABLE, PARAM) \ - TABLE->la_scale_regs.PARAM = ADJUST(TABLE->la_scale_regs.PARAM) - - #define ADJUST_PARAM_LA_SCALE_REG_HI(TABLE, PARAM, VALUE) \ - CLEAR_BIT(TABLE->la_scale_regs.PARAM, 23, 16) \ - TABLE->la_scale_regs.PARAM |= VALUE << 16 - - #define ADJUST_PARAM_LA_SCALE_REG_LO(TABLE, PARAM, VALUE) \ - CLEAR_BIT(TABLE->la_scale_regs.PARAM, 7, 0) \ - TABLE->la_scale_regs.PARAM |= VALUE - - u8 LA = ADJUST_INVERSE(128); //0x80 - ADJUST_PARAM_LA_SCALE_REG(table, mc_mll_mpcorer_ptsa_rate); //208 - ADJUST_PARAM_LA_SCALE_REG(table, mc_ptsa_grant_decrement); //4611 - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_xusb_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_xusb_1, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_tsec_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmca_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmcaa_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmc_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmcab_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmc_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmcab_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_ppcs_1, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_mpcore_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_avpc_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_gpu_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_gpu2_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_nvenc_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_nvdec_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_vic_0, LA); - ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_isp2_1, LA); - - ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_hc_0, ADJUST_INVERSE(0x16)); - ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_hc_1, LA); - ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_gpu_0, ADJUST_INVERSE(0x19)); - ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_gpu2_0, ADJUST_INVERSE(0x19)); - ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_vic_0, ADJUST_INVERSE(0x1d)); - ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_vi2_0, LA); - ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_isp2_1, LA); - - //Spread Spectrum Control - table->pllm_ss_ctrl1 = 0x0b55fe01; - table->pllm_ss_ctrl2 = 0x10170b55; - table->pllmb_ss_ctrl1 = 0x0b55fe01; - table->pllmb_ss_ctrl2 = 0x10170b55; - - table->dram_timings.t_rp = C.tRPpb; - table->dram_timings.t_rfc = C.tRFCab; - //table->dram_timings.rl = 32; - - table->emc_cfg_2 = 0x0011083d; -} - -void MemMtcTableCustomAdjust(MarikoMtcTable* table) { - if (C.mtcConf != CUSTOM_ADJ_ALL) - return; - - constexpr u32 MC_ARB_DIV = 4; - constexpr u32 MC_ARB_SFA = 2; - + WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(C.tRC)); + WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(C.tRFCab)); + WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(C.tRFCpb)); + WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(C.tRAS)); + WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(C.tRPpb)); + WRITE_PARAM_ALL_REG(table, emc_r2w, R2W); + WRITE_PARAM_ALL_REG(table, emc_w2r, W2R); + WRITE_PARAM_ALL_REG(table, emc_r2p, GET_CYCLE_CEIL(C.tRTP)); + WRITE_PARAM_ALL_REG(table, emc_w2p, WTP); + WRITE_PARAM_ALL_REG(table, emc_trtm, RTM); + WRITE_PARAM_ALL_REG(table, emc_twtm, WTM); + WRITE_PARAM_ALL_REG(table, emc_tratm, RATM); + WRITE_PARAM_ALL_REG(table, emc_twatm, WATM); + //WRITE_PARAM_ALL_REG(table, emc_tr2ref, GET_CYCLE_CEIL(tR2REF)); + WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(C.tRCD)); + WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(C.tRRD)); + WRITE_PARAM_ALL_REG(table, emc_rext, 26); + 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_pdex2wr, GET_CYCLE_CEIL(C.tXP)); + WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(C.tXP)); + WRITE_PARAM_ALL_REG(table, emc_pchg2pden, GET_CYCLE_CEIL(C.tCMDCKE)); + WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(C.tMRWCKEL)); + WRITE_PARAM_ALL_REG(table, emc_ar2pden, GET_CYCLE_CEIL(C.tCMDCKE)); + WRITE_PARAM_ALL_REG(table, emc_rw2pden, WTPDEN); + WRITE_PARAM_ALL_REG(table, emc_cke2pden, GET_CYCLE_CEIL(C.tCKELCS)); + //WRITE_PARAM_ALL_REG(table, emc_pdex2cke, GET_CYCLE_CEIL(tCSCKEH)); + WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(tPDEX2MRR)); + WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(C.tXSR), (u32)0x3fe)); + WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(C.tXSR), (u32)0x3fe)); + WRITE_PARAM_ALL_REG(table, emc_tcke, GET_CYCLE_CEIL(C.tCKE) + 1); + WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(C.tSR)); + WRITE_PARAM_ALL_REG(table, emc_tpd, GET_CYCLE_CEIL(C.tCKE)); + WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(C.tFAW)); + WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(C.tRPab)); + //WRITE_PARAM_ALL_REG(table, emc_tclkstable, GET_CYCLE_CEIL(tCKCKEH)); + WRITE_PARAM_ALL_REG(table, emc_tclkstop, GET_CYCLE_CEIL(C.tCKE) + 8); + WRITE_PARAM_ALL_REG(table, emc_trefbw, REFBW); + + ADJUST_PARAM_ALL_REG(table, emc_dyn_self_ref_control, ref); + + + #define CLEAR_BIT(BITS, HIGH, LOW) \ + BITS = BITS & ~( ((1u << HIGH) << 1u) - (1u << LOW) ); + + #define ADJUST(TARGET) (u32)CEIL(TARGET * (C.marikoEmcMaxClock / EmcClkOSLimit)) + #define ADJUST_INVERSE(TARGET) (u32)(TARGET * (EmcClkOSLimit / 1000) / (C.marikoEmcMaxClock / 1000)) + + // 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(C.tRCD) / MC_ARB_DIV) - 2) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rp, CEIL(GET_CYCLE_CEIL(C.tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rc, CEIL(GET_CYCLE_CEIL(C.tRC) / MC_ARB_DIV) - 1) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_ras, CEIL(GET_CYCLE_CEIL(C.tRAS) / MC_ARB_DIV) - 2) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_faw, CEIL(GET_CYCLE_CEIL(C.tFAW) / MC_ARB_DIV) - 1) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rrd, CEIL(GET_CYCLE_CEIL(C.tRRD) / MC_ARB_DIV) - 1) + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rap2pre, CEIL(GET_CYCLE_CEIL(C.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(C.tRFCpb) / MC_ARB_DIV)) + + u32 DA_TURNS = 0; + DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; //R2W TURN + DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; //W2R TURN + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_turns, DA_TURNS); + u32 DA_COVERS = 0; + u8 R_COVER = (table->burst_mc_regs.mc_emem_arb_timing_rap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; + u8 W_COVER = (table->burst_mc_regs.mc_emem_arb_timing_wap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; + DA_COVERS |= (u8)(table->burst_mc_regs.mc_emem_arb_timing_rc / 2); //RC COVER + DA_COVERS |= (R_COVER << 8); //RCD_R COVER + DA_COVERS |= (W_COVER << 16); //RCD_W COVER + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_covers, DA_COVERS); + + CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 7, 0); + table->burst_mc_regs.mc_emem_arb_misc0 |= u8(table->burst_mc_regs.mc_emem_arb_timing_rc + 1); //BC2AA_HOLDOFF + CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 14, 8); + table->burst_mc_regs.mc_emem_arb_misc0 |= u8((ADJUST(0x24) << 8)); //PRIORITY_INVERSION_THRESHOLD + CLEAR_BIT(table->burst_mc_regs.mc_emem_arb_misc0, 20, 16); + table->burst_mc_regs.mc_emem_arb_misc0 |= u8((ADJUST(12) << 16)); //PRIORITY_INVERSION_ISO_THRESHOLD + + // updown registers + #define ADJUST_PARAM_LA_SCALE_REG(TABLE, PARAM) \ + TABLE->la_scale_regs.PARAM = ADJUST(TABLE->la_scale_regs.PARAM) + + #define ADJUST_PARAM_LA_SCALE_REG_HI(TABLE, PARAM, VALUE) \ + CLEAR_BIT(TABLE->la_scale_regs.PARAM, 23, 16) \ + TABLE->la_scale_regs.PARAM |= VALUE << 16 + + #define ADJUST_PARAM_LA_SCALE_REG_LO(TABLE, PARAM, VALUE) \ + CLEAR_BIT(TABLE->la_scale_regs.PARAM, 7, 0) \ + TABLE->la_scale_regs.PARAM |= VALUE + + u8 LA = ADJUST_INVERSE(128); //0x80 + ADJUST_PARAM_LA_SCALE_REG(table, mc_mll_mpcorer_ptsa_rate); //208 + ADJUST_PARAM_LA_SCALE_REG(table, mc_ptsa_grant_decrement); //4611 + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_xusb_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_xusb_1, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_tsec_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmca_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmcaa_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmc_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmcab_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmc_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_sdmmcab_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_ppcs_1, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_mpcore_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_avpc_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_gpu_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_gpu2_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_nvenc_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_nvdec_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_vic_0, LA); + ADJUST_PARAM_LA_SCALE_REG_HI(table, mc_latency_allowance_isp2_1, LA); + + ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_hc_0, ADJUST_INVERSE(0x16)); + ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_hc_1, LA); + ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_gpu_0, ADJUST_INVERSE(0x19)); + ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_gpu2_0, ADJUST_INVERSE(0x19)); + ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_vic_0, ADJUST_INVERSE(0x1d)); + ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_vi2_0, LA); + ADJUST_PARAM_LA_SCALE_REG_LO(table, mc_latency_allowance_isp2_1, LA); + + //Spread Spectrum Control + table->pllm_ss_ctrl1 = 0x0b55fe01; + table->pllm_ss_ctrl2 = 0x10170b55; + table->pllmb_ss_ctrl1 = 0x0b55fe01; + table->pllmb_ss_ctrl2 = 0x10170b55; + + table->dram_timings.t_rp = C.tRPpb; + table->dram_timings.t_rfc = C.tRFCab; + //table->dram_timings.rl = 32; + + table->emc_cfg_2 = 0x0011083d; + } + + void MemMtcTableCustomAdjust(MarikoMtcTable* table) { + if (C.mtcConf != CUSTOM_ADJ_ALL) + return; + + constexpr u32 MC_ARB_DIV = 4; + constexpr u32 MC_ARB_SFA = 2; + WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(C.tRC)); WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(C.tRAS)); WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(C.tRPpb)); @@ -345,228 +346,228 @@ void MemMtcTableCustomAdjust(MarikoMtcTable* table) { table->burst_mc_regs.mc_emem_arb_timing_rc = CEIL(GET_CYCLE_CEIL(C.tRC) / MC_ARB_DIV) - 1; table->burst_mc_regs.mc_emem_arb_timing_rp = CEIL(GET_CYCLE_CEIL(C.tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA; table->burst_mc_regs.mc_emem_arb_timing_ras = CEIL(GET_CYCLE_CEIL(C.tRAS) / MC_ARB_DIV) - 2; - - u32 DA_TURNS = 0; - DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; //R2W TURN - DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; //W2R TURN - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_turns, DA_TURNS); - u32 DA_COVERS = 0; - u8 R_COVER = (table->burst_mc_regs.mc_emem_arb_timing_rap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; - u8 W_COVER = (table->burst_mc_regs.mc_emem_arb_timing_wap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; - DA_COVERS |= (u8)(table->burst_mc_regs.mc_emem_arb_timing_rc / 2); //RC COVER - DA_COVERS |= (R_COVER << 8); //RCD_R COVER - DA_COVERS |= (W_COVER << 16); //RCD_W COVER - WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_covers, DA_COVERS); -} - -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; -} - -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, table_alt); - MemMtcTableCustomAdjust(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 } - }; - - 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); - } -} - -} \ No newline at end of file + + u32 DA_TURNS = 0; + DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16; //R2W TURN + DA_TURNS |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24; //W2R TURN + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_turns, DA_TURNS); + u32 DA_COVERS = 0; + u8 R_COVER = (table->burst_mc_regs.mc_emem_arb_timing_rap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; + u8 W_COVER = (table->burst_mc_regs.mc_emem_arb_timing_wap2pre + table->burst_mc_regs.mc_emem_arb_timing_rp + table->burst_mc_regs.mc_emem_arb_timing_rcd) / 2; + DA_COVERS |= (u8)(table->burst_mc_regs.mc_emem_arb_timing_rc / 2); //RC COVER + DA_COVERS |= (R_COVER << 8); //RCD_R COVER + DA_COVERS |= (W_COVER << 16); //RCD_W COVER + WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_da_covers, DA_COVERS); + } + + 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; + } + + 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, table_alt); + MemMtcTableCustomAdjust(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 } + }; + + 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); + } + } + + } \ No newline at end of file