From af8912321c297ed21f8114761f274a5d040fa9d5 Mon Sep 17 00:00:00 2001
From: Lightos1 <124387232+Lightos1@users.noreply.github.com>
Date: Tue, 28 Jul 2026 19:54:07 +0200
Subject: [PATCH] ldr: major refactor part 1
---
.../erista/calculate_timings_erista.cpp | 2 +-
.../erista/calculate_timings_erista.hpp | 0
.../source/oc/pcv/erista/pcv_erista.cpp | 97 ++
.../source/oc/pcv/erista/pcv_erista.hpp | 42 +
.../source/oc/pcv/erista/pcv_erista_cpu.cpp | 108 ++
.../source/oc/pcv/erista/pcv_erista_cpu.hpp | 66 +
.../source/oc/pcv/erista/pcv_erista_gpu.cpp | 123 ++
.../source/oc/pcv/erista/pcv_erista_gpu.hpp | 82 +
.../pcv_erista_mtc.cpp} | 273 +--
.../source/oc/pcv/erista/pcv_erista_mtc.hpp | 92 +
.../mariko/calculate_timings_mariko.cpp} | 408 ++---
.../mariko/calculate_timings_mariko.hpp} | 48 +-
.../source/oc/pcv/mariko/pcv_mariko.cpp | 588 +++++++
.../source/oc/pcv/{ => mariko}/pcv_mariko.hpp | 8 +-
.../source/oc/pcv/mariko/pcv_mariko_cpu.cpp | 237 +++
.../source/oc/pcv/mariko/pcv_mariko_cpu.hpp | 34 +
.../source/oc/pcv/mariko/pcv_mariko_gpu.cpp | 140 ++
.../source/oc/pcv/mariko/pcv_mariko_gpu.hpp | 35 +
.../source/oc/pcv/mariko/pcv_mariko_mtc.cpp | 676 ++++++++
.../source/oc/pcv/mariko/pcv_mariko_mtc.hpp | 35 +
.../oc/{ => pcv}/mariko/timing_tables.cpp | 94 +-
.../oc/{ => pcv}/mariko/timing_tables.hpp | 62 +-
.../stratosphere/loader/source/oc/pcv/pcv.hpp | 11 +-
.../loader/source/oc/pcv/pcv_erista.hpp | 165 --
.../loader/source/oc/pcv/pcv_hook.hpp | 3 +
.../loader/source/oc/pcv/pcv_mariko.cpp | 1545 -----------------
26 files changed, 2695 insertions(+), 2279 deletions(-)
rename Source/Atmosphere/stratosphere/loader/source/oc/{ => pcv}/erista/calculate_timings_erista.cpp (99%)
rename Source/Atmosphere/stratosphere/loader/source/oc/{ => pcv}/erista/calculate_timings_erista.hpp (100%)
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.cpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.hpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.cpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.hpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp
rename Source/Atmosphere/stratosphere/loader/source/oc/pcv/{pcv_erista.cpp => erista/pcv_erista_mtc.cpp} (72%)
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp
rename Source/Atmosphere/stratosphere/loader/source/oc/{mariko/calculate_timings.cpp => pcv/mariko/calculate_timings_mariko.cpp} (96%)
rename Source/Atmosphere/stratosphere/loader/source/oc/{mariko/calculate_timings.hpp => pcv/mariko/calculate_timings_mariko.hpp} (96%)
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp
rename Source/Atmosphere/stratosphere/loader/source/oc/pcv/{ => mariko}/pcv_mariko.hpp (99%)
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp
create mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp
rename Source/Atmosphere/stratosphere/loader/source/oc/{ => pcv}/mariko/timing_tables.cpp (96%)
rename Source/Atmosphere/stratosphere/loader/source/oc/{ => pcv}/mariko/timing_tables.hpp (96%)
delete mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp
delete mode 100644 Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp
similarity index 99%
rename from Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.cpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp
index a8106726..fc6ae8f8 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.cpp
@@ -14,7 +14,7 @@
* along with this program. If not, see .
*/
-#include "../mtc_timing_value.hpp"
+#include "../../mtc_timing_value.hpp"
namespace ams::ldr::hoc::pcv::erista {
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.hpp
similarity index 100%
rename from Source/Atmosphere/stratosphere/loader/source/oc/erista/calculate_timings_erista.hpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/calculate_timings_erista.hpp
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.cpp
new file mode 100644
index 00000000..742776cb
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "../pcv.hpp"
+#include "../../mtc_timing_value.hpp"
+#include "pcv_erista_cpu.hpp"
+#include "pcv_erista_gpu.hpp"
+#include "pcv_erista_mtc.hpp"
+#include "calculate_timings_erista.hpp"
+
+namespace ams::ldr::hoc::pcv::erista {
+
+ DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
+
+ u32 *nsoStart;
+
+ Result InstallHooks() {
+ R_TRY(Hooks().CheckEnabled());
+
+ R_TRY(Hooks().CopyPayload());
+
+ auto *data = Hooks().BindData(e_HookPayloadData);
+ R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
+
+ R_TRY(MtcInstallHooks(data));
+
+ R_SUCCEED();
+ }
+
+ void Patch(uintptr_t mapped_nso, size_t nso_size) {
+ nsoStart = reinterpret_cast(mapped_nso);
+ MtcGenerateFreqTables();
+
+ u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
+ u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
+
+ PatcherEntry patches[] = {
+ {"CPU Freq Table", CpuFreqCvbTable, 1, nullptr, CpuCvbDefaultMaxFreq },
+ {"CPU Volt DVFS", &CpuVoltDvfs, 1, nullptr, CpuVminOfficial },
+ {"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
+ {"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
+ {"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
+ {"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
+ {"GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq },
+ {"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
+ {"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
+ // {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
+ {"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn },
+ {"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
+ {"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
+ {"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
+ {"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS },
+ };
+
+ for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); 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())) {
+ panic::SmcError(panic::Patch);
+
+ CRASH(entry.description);
+ }
+ }
+
+ if (R_FAILED(InstallHooks())) {
+ panic::SmcError(panic::Patch);
+ }
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.hpp
new file mode 100644
index 00000000..8a530486
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista.hpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "../../oc_common.hpp"
+#include "../pcv_common.hpp"
+#include "../pcv_asm.hpp"
+#include "../pcv_hook.hpp"
+
+namespace ams::ldr::hoc::pcv::erista {
+
+ struct HookPayloadData {
+ struct {
+ EristaMtcTable *mtcTable;
+ u32 mtcCount;
+ } mtcTableAsm;
+ };
+ DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
+
+ extern u32 *nsoStart;
+
+ void Patch(uintptr_t mapped_nso, size_t nso_size);
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.cpp
new file mode 100644
index 00000000..8fddf0af
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "../pcv.hpp"
+
+namespace ams::ldr::hoc::pcv::erista {
+
+ Result CpuVoltDvfs(u32 *ptr) {
+ if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) {
+ R_THROW(ldr::ResultInvalidCpuMinVolt());
+ }
+
+ if (C.eristaCpuVmin) {
+ PATCH_OFFSET(ptr, C.eristaCpuVmin);
+ }
+
+ if (C.eristaCpuUV) {
+ PATCH_OFFSET(ptr - 2, C.eristaCpuVmin);
+ }
+
+ if (C.eristaCpuMaxVolt) {
+ PATCH_OFFSET(ptr + 5, C.eristaCpuMaxVolt);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result CpuVoltThermals(u32 *ptr) {
+ if (std::memcmp(ptr - 6, cpuVoltageThermalPattern, sizeof(cpuVoltageThermalPattern))) {
+ R_THROW(ldr::ResultInvalidCpuMinVolt());
+ }
+
+ if (C.eristaCpuVmin) {
+ PATCH_OFFSET( ptr, C.eristaCpuVmin);
+ PATCH_OFFSET(ptr + 3, C.eristaCpuVmin);
+ PATCH_OFFSET(ptr + 6, C.eristaCpuVmin);
+ }
+
+ if (C.eristaCpuMaxVolt) {
+ PATCH_OFFSET(ptr - 2, C.eristaCpuMaxVolt);
+ PATCH_OFFSET(ptr + 1, C.eristaCpuMaxVolt);
+ PATCH_OFFSET(ptr + 4, C.eristaCpuMaxVolt);
+ PATCH_OFFSET(ptr + 7, C.eristaCpuMaxVolt);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result CpuVoltDfll(u32* ptr) {
+ CvbCpuDfllData *entry = reinterpret_cast(ptr);
+
+ R_UNLESS(entry->tune0_low == 0xFFEAD0FF, ldr::ResultInvalidCpuVoltDfllEntry());
+ R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
+ R_UNLESS(entry->tune1_low == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
+ R_UNLESS(entry->tune1_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
+
+ if (!C.eristaCpuUV) {
+ R_SKIP();
+ }
+
+ switch (C.eristaCpuUV) {
+ case 1:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27007ff);
+ break;
+ case 2:
+ PATCH_OFFSET(&(entry->tune0_high), 0xefff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
+ break;
+ case 3:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
+ break;
+ case 4:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfdf);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27a07ff);
+ break;
+ case 5:
+ PATCH_OFFSET(&(entry->tune0_high), 0xcfdf);
+ PATCH_OFFSET(&(entry->tune1_high), 0x37007ff);
+ break;
+ default:
+ break;
+ }
+
+ R_SUCCEED();
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.hpp
new file mode 100644
index 00000000..e028ff54
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_cpu.hpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+#include "pcv_erista.hpp"
+
+namespace ams::ldr::hoc::pcv::erista {
+
+ constexpr cvb_entry_t CpuCvbTableDefault[] = {
+ // CPU_PLL_CVB_TABLE_ODN
+ { 204000, {721094}, { } },
+ { 306000, {754040}, { } },
+ { 408000, {786986}, { } },
+ { 510000, {819932}, { } },
+ { 612000, {852878}, { } },
+ { 714000, {885824}, { } },
+ { 816000, {918770}, { } },
+ { 918000, {951716}, { } },
+ { 1020000, {984662}, { -2875621, 358099, -8585} },
+ { 1122000, {1017608}, { -52225, 104159, -2816} },
+ { 1224000, {1050554}, { 1076868, 8356, -727} },
+ { 1326000, {1083500}, { 2208191, -84659, 1240} },
+ { 1428000, {1116446}, { 2519460, -105063, 1611} },
+ { 1581000, {1130000}, { 2889664, -122173, 1834} },
+ { 1683000, {1168000}, { 5100873, -279186, 4747} },
+ { 1785000, {1227500}, { 5100873, -279186, 4747} },
+ { },
+ };
+
+ constexpr u32 CpuVoltOfficial = 1227;
+ constexpr u32 CpuVminOfficial = 825;
+ constexpr u32 CpuTune0Low = 0xFFEAD0FF;
+
+ constexpr u32 CpuVoltL4T = 1257'000;
+
+ static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 };
+ static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "Invalid cpuVoltDvfsPattern size");
+
+ static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 };
+ static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "Invalid cpuVoltageThermalPattern size");
+
+
+ Result CpuVoltDvfs(u32 *ptr);
+ Result CpuVoltThermals(u32 *ptr);
+ Result CpuVoltDfll(u32* ptr);
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp
new file mode 100644
index 00000000..937af7d7
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "../pcv.hpp"
+#include "../pcv_asm.hpp"
+
+namespace ams::ldr::hoc::pcv::erista {
+
+ Result GpuVoltDVFS(u32 *ptr) {
+ if (std::memcmp(ptr, gpuVoltDvfsPattern, sizeof(gpuVoltDvfsPattern))) {
+ R_THROW(ldr::ResultInvalidGpuDvfs());
+ }
+
+ if (C.eristaGpuVmin) {
+ PATCH_OFFSET(ptr, C.eristaGpuVmin);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result GpuVoltThermals(u32 *ptr) {
+ if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
+ R_THROW(ldr::ResultInvalidGpuDvfs());
+ }
+
+ if (C.eristaGpuVmin) {
+ PATCH_OFFSET(ptr, C.eristaGpuVmin);
+ PATCH_OFFSET(ptr + 3, C.eristaGpuVmin);
+ PATCH_OFFSET(ptr + 6, C.eristaGpuVmin);
+ PATCH_OFFSET(ptr + 9, C.eristaGpuVmin);
+ PATCH_OFFSET(ptr + 12, C.eristaGpuVmin);
+ }
+
+ 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, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[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.eristaGpuUV) {
+ case 0:
+ max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
+ break;
+ case 1:
+ max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq;
+ break;
+ case 2:
+ max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHiOPT)->freq;
+ break;
+ default:
+ max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
+ break;
+ }
+
+ u32 asm_patch[2] = {
+ asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
+ asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
+ };
+
+ PATCH_OFFSET(ptr32, asm_patch[0]);
+ PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
+
+ R_SUCCEED();
+ }
+
+ Result GpuFreqPllMax(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();
+ }
+
+ // patch out 1305MHz limit on erista, don't use this!
+ // Result GpuFreqPllLimit(u32 *ptr) {
+ // u32 prev_freq = *(ptr - 1);
+
+ // if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
+ // R_THROW(ldr::ResultInvalidGpuPllEntry());
+ // }
+
+ // PATCH_OFFSET(ptr, 3600000);
+
+ // R_SUCCEED();
+ // }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp
new file mode 100644
index 00000000..cc964628
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_gpu.hpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "pcv_erista.hpp"
+
+namespace ams::ldr::hoc::pcv::erista {
+
+ constexpr u32 GpuClkPllLimit = 2'600'000;
+ constexpr u32 GpuClkPllMax = 921'600'000;
+ constexpr u32 GpuVminOfficial = 810;
+
+ static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, };
+ static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern");
+
+ static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 };
+ static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "Invalid gpuVoltageThermalPattern size");
+
+ /* GPU Max Clock asm Pattern:
+ *
+ * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
+ * sf | opc | | hw | imm16 | Rd
+ * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
+ * 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
+ *
+ * MOVK W11, #0xE, LSL#16 16 0xE 0xB (11)
+ * sf | opc | | hw | imm16 | Rd
+ * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
+ * 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
+ */
+ inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
+
+ inline bool GpuMaxClockPatternFn(u32 *ptr32) {
+ return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
+ };
+
+ constexpr cvb_entry_t GpuCvbTableDefault[] = {
+ // NA_FREQ_CVB_TABLE
+ { 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } },
+ { 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } },
+ { 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } },
+ { 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } },
+ { 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } },
+ { 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } },
+ { 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } },
+ { 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } },
+ { 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } },
+ { 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } },
+ { 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } },
+ { 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } },
+ { },
+ };
+
+ Result GpuVoltDVFS(u32 *ptr);
+ Result GpuVoltThermals(u32 *ptr);
+ Result GpuFreqMaxAsm(u32 *ptr32);
+ Result GpuFreqPllMax(u32 *ptr);
+
+ // patch out 1305MHz limit on erista, don't use this!
+ // Result GpuFreqPllLimit(u32 *ptr);
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp
similarity index 72%
rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp
index c4d51f97..6b2d4321 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.cpp
@@ -21,181 +21,22 @@
*/
#include
-#include "pcv.hpp"
-#include "../mtc_timing_value.hpp"
-#include "../erista/calculate_timings_erista.hpp"
+#include "../pcv.hpp"
+#include "../../mtc_timing_value.hpp"
+#include "calculate_timings_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
- struct HookPayloadData {
- struct {
- EristaMtcTable *mtcTable;
- u32 mtcCount;
- } mtcTableAsm;
- };
- DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
-
namespace {
std::vector newEmcList;
- u32 *nsoStart;
- u32 *nsoEnd;
struct {
u32 *getEristaMtcTableFnSite = nullptr;
EristaMtcTable *mtcTable = nullptr;
- bool foundMtcTablePattern = false;
+ bool foundMtcTablePattern = false;
} getMtcTableCache;
}
- Result CpuVoltDvfs(u32 *ptr) {
- if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) {
- R_THROW(ldr::ResultInvalidCpuMinVolt());
- }
-
- if (C.eristaCpuVmin) {
- PATCH_OFFSET(ptr, C.eristaCpuVmin);
- }
-
- if (C.eristaCpuUV) {
- PATCH_OFFSET(ptr - 2, C.eristaCpuVmin);
- }
-
- if (C.eristaCpuMaxVolt) {
- PATCH_OFFSET(ptr + 5, C.eristaCpuMaxVolt);
- }
-
- R_SUCCEED();
- }
-
- Result CpuVoltThermals(u32 *ptr) {
- if (std::memcmp(ptr - 6, cpuVoltageThermalPattern, sizeof(cpuVoltageThermalPattern))) {
- R_THROW(ldr::ResultInvalidCpuMinVolt());
- }
-
- if (C.eristaCpuVmin) {
- PATCH_OFFSET( ptr, C.eristaCpuVmin);
- PATCH_OFFSET(ptr + 3, C.eristaCpuVmin);
- PATCH_OFFSET(ptr + 6, C.eristaCpuVmin);
- }
-
- if (C.eristaCpuMaxVolt) {
- PATCH_OFFSET(ptr - 2, C.eristaCpuMaxVolt);
- PATCH_OFFSET(ptr + 1, C.eristaCpuMaxVolt);
- PATCH_OFFSET(ptr + 4, C.eristaCpuMaxVolt);
- PATCH_OFFSET(ptr + 7, C.eristaCpuMaxVolt);
- }
-
- R_SUCCEED();
- }
-
- Result CpuVoltDfll(u32* ptr) {
- CvbCpuDfllData *entry = reinterpret_cast(ptr);
-
- R_UNLESS(entry->tune0_low == 0xFFEAD0FF, ldr::ResultInvalidCpuVoltDfllEntry());
- R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
- R_UNLESS(entry->tune1_low == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
- R_UNLESS(entry->tune1_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
-
- if (!C.eristaCpuUV) {
- R_SKIP();
- }
-
- switch(C.eristaCpuUV) {
- case 1:
- PATCH_OFFSET(&(entry->tune0_high), 0xffff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27007ff);
- break;
- case 2:
- PATCH_OFFSET(&(entry->tune0_high), 0xefff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
- break;
- case 3:
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
- break;
- case 4:
- PATCH_OFFSET(&(entry->tune0_high), 0xdfdf);
- PATCH_OFFSET(&(entry->tune1_high), 0x27a07ff);
- break;
- case 5:
- PATCH_OFFSET(&(entry->tune0_high), 0xcfdf);
- PATCH_OFFSET(&(entry->tune1_high), 0x37007ff);
- break;
- default:
- break;
- }
-
- R_SUCCEED();
- }
-
- Result GpuVoltDVFS(u32 *ptr) {
- if (std::memcmp(ptr, gpuVoltDvfsPattern, sizeof(gpuVoltDvfsPattern))) {
- R_THROW(ldr::ResultInvalidGpuDvfs());
- }
-
- if (C.eristaGpuVmin) {
- PATCH_OFFSET(ptr, C.eristaGpuVmin);
- }
-
- R_SUCCEED();
- }
-
- Result GpuVoltThermals(u32 *ptr) {
- if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
- R_THROW(ldr::ResultInvalidGpuDvfs());
- }
-
- if (C.eristaGpuVmin) {
- PATCH_OFFSET(ptr, C.eristaGpuVmin);
- PATCH_OFFSET(ptr + 3, C.eristaGpuVmin);
- PATCH_OFFSET(ptr + 6, C.eristaGpuVmin);
- PATCH_OFFSET(ptr + 9, C.eristaGpuVmin);
- PATCH_OFFSET(ptr + 12, C.eristaGpuVmin);
- }
-
- 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, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[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.eristaGpuUV) {
- case 0:
- max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
- break;
- case 1:
- max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq;
- break;
- case 2:
- max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHiOPT)->freq;
- break;
- default:
- max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
- break;
- }
-
- u32 asm_patch[2] = {
- asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
- asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
- };
-
- PATCH_OFFSET(ptr32, asm_patch[0]);
- PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
-
- R_SUCCEED();
- }
-
/* Note: This does not have proper timings, so base latency adjustment will not work. */
/* However, it may still achieve a slightly higher frequency, but not as much as it could be. */
/* I'm certainly not insane enough to attempt this pain again, so this will have to do *for now*. */
@@ -572,33 +413,6 @@ namespace ams::ldr::hoc::pcv::erista {
R_SUCCEED();
}
- Result GpuFreqPllMax(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();
- }
-
- // patch out 1305MHz limit on erista, don't use this!
- // Result GpuFreqPllLimit(u32 *ptr) {
- // u32 prev_freq = *(ptr - 1);
-
- // if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
- // R_THROW(ldr::ResultInvalidGpuPllEntry());
- // }
-
- // PATCH_OFFSET(ptr, 3600000);
-
- // R_SUCCEED();
- // }
-
HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count) {
const HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
@@ -606,6 +420,18 @@ namespace ams::ldr::hoc::pcv::erista {
return data->mtcTableAsm.mtcTable;
}
+ Result MtcInstallHooks(HookPayloadData *data) {
+ R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern());
+
+ /* Copy the data to the payload. */
+ data->mtcTableAsm.mtcTable = reinterpret_cast(Hooks().ToVa(getMtcTableCache.mtcTable));
+ data->mtcTableAsm.mtcCount = newEmcList.size();
+
+ R_TRY(INSTALL_IMPL_HOOK(getMtcTableCache.getEristaMtcTableFnSite, GetEristaMtcTableImpl));
+
+ R_SUCCEED();
+ }
+
Result MemMtcTableAsm(u32 *ptr) {
/* Return if the pattern was already found. */
/* This pattern happens multiple times in this function., we only need to find it once. */
@@ -650,71 +476,4 @@ namespace ams::ldr::hoc::pcv::erista {
R_SUCCEED();
}
- Result InstallHooks() {
- R_TRY(Hooks().CheckEnabled());
-
- R_TRY(Hooks().CopyPayload());
-
- R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern());
-
- /* Copy the data to the payload. */
- auto *data = Hooks().BindData(e_HookPayloadData);
- R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
-
- data->mtcTableAsm.mtcTable = reinterpret_cast(Hooks().ToVa(getMtcTableCache.mtcTable));
- data->mtcTableAsm.mtcCount = newEmcList.size();
- R_TRY(INSTALL_IMPL_HOOK(getMtcTableCache.getEristaMtcTableFnSite, GetEristaMtcTableImpl));
-
- R_SUCCEED();
- }
-
- void Patch(uintptr_t mapped_nso, size_t nso_size) {
- nsoStart = reinterpret_cast(mapped_nso);
- nsoEnd = reinterpret_cast(mapped_nso + nso_size);
- MtcGenerateFreqTables();
-
- u32 CpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
- u32 GpuCvbDefaultMaxFreq = static_cast(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
-
- PatcherEntry patches[] = {
- {"CPU Freq Table", CpuFreqCvbTable, 1, nullptr, CpuCvbDefaultMaxFreq },
- {"CPU Volt DVFS", &CpuVoltDvfs, 1, nullptr, CpuVminOfficial },
- {"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
- {"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
- {"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
- {"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
- {"GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq },
- {"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
- {"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
- // {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
- {"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn },
- {"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
- {"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
- {"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
- {"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS },
- };
-
- for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); 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\n", entry.description, entry.patched_count);
- if (R_FAILED(entry.CheckResult())) {
- panic::SmcError(panic::Patch);
-
- CRASH(entry.description);
- }
- }
-
- if (R_FAILED(InstallHooks())) {
- panic::SmcError(panic::Patch);
- }
- }
-
}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp
new file mode 100644
index 00000000..5c1c5100
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/erista/pcv_erista_mtc.hpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "pcv_erista.hpp"
+#include "../../mtc_timing_table.hpp"
+
+namespace ams::ldr::hoc::pcv::erista {
+
+ constexpr u32 EmcListDefault[] = { 40800, 68000, 102000, 204000, 408000, 665600, 800000, 1065600, 1331200, 1600000, };
+ constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
+ constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
+
+ constexpr u32 MemVoltHOS = 1125'000;
+ constexpr u32 EmcClkPllmLimit = 1866'000'000;
+
+ constexpr u32 MTC_TABLE_REV = 7;
+ constexpr u32 MtcTableCountDefault = 10;
+
+ constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault;
+ constexpr u32 MtcFullTableCount = 3;
+
+ /* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
+ enum DramId {
+ ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0,
+ ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1,
+ ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2,
+ ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4,
+ ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7,
+ };
+
+ enum MtcTableIndex {
+ T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH, WT:C */
+ T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */
+ T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */
+ MtcTableIndex_Invalid = 3,
+ };
+
+ struct MtcDramIndex {
+ DramId dramId;
+ MtcTableIndex index;
+ };
+
+ /* TODO: Test 6gb and 8gb. */
+ const inline MtcDramIndex mtcIndexTable[] = {
+ { ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, },
+ { ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC, T210SdevEmcDvfsTableS4gb01, },
+ { ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, },
+ { ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX, T210SdevEmcDvfsTableS6gb01, },
+ { ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, },
+ };
+
+ constexpr u32 MtcBrAsm = 0xD61F0140;
+ constexpr u32 MtcMovAsm = 0x52800148;
+ constexpr u32 MtcAdrpAsm = 0xD0000081;
+ constexpr u32 MtcBlIns = 0x97ffae64;
+ constexpr u32 MtcAddAsm = 0x91131821;
+
+ ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
+ /* This builds an address that gets returned, so the register must be x0 by convention. */
+ return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
+ }
+
+ Result MemFreqMtcTable(u32 *ptr);
+ void MtcGenerateFreqTables();
+ Result MemFreqMax(u32 *ptr);
+ HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count);
+ Result MemMtcTableAsm(u32 *ptr);
+
+ Result MtcInstallHooks(HookPayloadData *data);
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp
similarity index 96%
rename from Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.cpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp
index f83353fb..fab24dbf 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.cpp
@@ -1,204 +1,204 @@
-/*
- * Copyright (c) Lightos_
- *
- * 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.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include "../mtc_timing_value.hpp"
-#include "timing_tables.hpp"
-
-namespace ams::ldr::hoc::pcv::mariko {
-
- void GetRext() {
- if (auto r = FindRext()) {
- rext = r->rext;
- return;
- }
-
- /* > 3200 */
- rext = 0x1E;
- }
-
- void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
- latency += index * latencyStep;
- }
-
- static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
- s32 maxIndex = -1;
- for (u32 i = 0; i < latencySize; ++i) {
- if (latencyArray[i]) {
- maxIndex = i;
- }
- }
-
- return maxIndex;
- }
-
- void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
- if (freq > 1600'000 && freq <= 1862'400) { /* 1866tRWL */
- latency += latencyStep * 2;
- } else { /* 2133tRWL */
- latency += latencyStep * 3;
- }
- }
-
- void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
- for (u32 i = 0; i <= indexMax; ++i) {
- if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
- SwitchLatency(latency, i, latencyStep);
- return;
- }
- }
-
- SwitchLatency(latency, indexMax, latencyStep);
- }
-
- void HandleLatency(u32 freq) {
- static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
- static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
- constexpr u32 ReadLatencyStep = 4;
- constexpr u32 WriteLatencyStep = 2;
- bool autoLatencyRead = false, autoLatencyWrite = false;
-
- if (rlIndexMax == -1) {
- AutoLatency(RL, freq, ReadLatencyStep);
- autoLatencyRead = true;
- }
-
- if (wlIndexMax == -1) {
- AutoLatency(WL, freq, WriteLatencyStep);
- autoLatencyWrite = true;
- }
-
- if (autoLatencyRead && autoLatencyWrite) {
- return;
- }
-
- if (!autoLatencyRead) {
- HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
- }
-
- if (!autoLatencyWrite) {
- HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
- }
- }
-
- void CalculateMrw2() {
- static const u8 rlMapDBI[8] = {
- 6, 12, 16, 22, 28, 32, 36, 40
- };
-
- static const u8 wlMapSetA[8] = {
- 4, 6, 8, 10, 12, 14, 16, 18
- };
-
- u32 rlIndex = 0;
- u32 wlIndex = 0;
-
- for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
- if (rlMapDBI[i] == RL) {
- rlIndex = i;
- break;
- }
- }
-
- for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
- if (wlMapSetA[i] == WL) {
- wlIndex = i;
- break;
- }
- }
-
- /* DBI is always enabled. */
- mrw2 = static_cast(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
- }
-
- void CalculateTimings(double tCK_avg, u32 freq) {
- RL = RL_1331;
- WL = WL_1331;
-
- HandleLatency(freq);
-
- GetRext();
-
- /* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */
- /* This is a lazy workaround until I find the issue... */
- const bool lowFreq = freq < C.timingEmcTbreak;
-
- tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
- tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
- tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
- tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
- tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
-
- u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
- u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
-
- s32 finetRTW = C.fineTune_t6_tRTW;
- s32 finetWTR = C.fineTune_t7_tWTR;
-
- u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
- refresh_raw = 0xFFFF;
- if (tREFI != 6) {
- refresh_raw = CEIL(tREFpb_values[tREFI] / tCK_avg) - 0x40;
- refresh_raw = MIN(refresh_raw, static_cast(0xFFFF));
- }
-
- tRC = tRAS + tRPpb;
- tRFCab = tRFCpb * 2;
- tXSR = static_cast(tRFCab + 7.5);
- tFAW = static_cast(tRRD * 4.0);
- tRPab = tRPpb + 3;
-
- tR2P = CEIL((RL * 0.426) - 2.0);
- tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW;
- tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
- tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
-
- rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017);
- qpop = rdv - 14;
- quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
- quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg);
- einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
- einput = quse - CEIL(9.928 / tCK_avg);
- u32 qrst_duration = FLOOR(8.399 - tCK_avg);
- u32 qrstLow = MAX(static_cast(einput - qrst_duration - 2), static_cast(0));
- qrst = PACK_U32(qrst_duration, qrstLow);
- ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
- qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
- tW2P = (CEIL(WL * 1.7303) * 2) - 5;
- tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast(tW2P))) + (BL / 2));
- tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
- tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
- tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
-
- wdv = WL;
- wsv = WL - 2;
- wev = 0xA + (WL - 14);
-
- u32 obdlyHigh = 3 / FLOOR(MIN(static_cast(2), tCK_avg * (WL - 7)));
- u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
- obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
-
- pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
-
- tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
-
- u32 tMMRI = tRCD + (tCK_avg * 3);
- pdex2mrr = tMMRI + 10;
-
- CalculateMrw2();
- }
-
-}
+/*
+ * Copyright (c) Lightos_
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include "../../mtc_timing_value.hpp"
+#include "timing_tables.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ void GetRext() {
+ if (auto r = FindRext()) {
+ rext = r->rext;
+ return;
+ }
+
+ /* > 3200 */
+ rext = 0x1E;
+ }
+
+ void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
+ latency += index * latencyStep;
+ }
+
+ static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
+ s32 maxIndex = -1;
+ for (u32 i = 0; i < latencySize; ++i) {
+ if (latencyArray[i]) {
+ maxIndex = i;
+ }
+ }
+
+ return maxIndex;
+ }
+
+ void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
+ if (freq > 1600'000 && freq <= 1862'400) { /* 1866tRWL */
+ latency += latencyStep * 2;
+ } else { /* 2133tRWL */
+ latency += latencyStep * 3;
+ }
+ }
+
+ void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
+ for (u32 i = 0; i <= indexMax; ++i) {
+ if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
+ SwitchLatency(latency, i, latencyStep);
+ return;
+ }
+ }
+
+ SwitchLatency(latency, indexMax, latencyStep);
+ }
+
+ void HandleLatency(u32 freq) {
+ static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
+ static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
+ constexpr u32 ReadLatencyStep = 4;
+ constexpr u32 WriteLatencyStep = 2;
+ bool autoLatencyRead = false, autoLatencyWrite = false;
+
+ if (rlIndexMax == -1) {
+ AutoLatency(RL, freq, ReadLatencyStep);
+ autoLatencyRead = true;
+ }
+
+ if (wlIndexMax == -1) {
+ AutoLatency(WL, freq, WriteLatencyStep);
+ autoLatencyWrite = true;
+ }
+
+ if (autoLatencyRead && autoLatencyWrite) {
+ return;
+ }
+
+ if (!autoLatencyRead) {
+ HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
+ }
+
+ if (!autoLatencyWrite) {
+ HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
+ }
+ }
+
+ void CalculateMrw2() {
+ static const u8 rlMapDBI[8] = {
+ 6, 12, 16, 22, 28, 32, 36, 40
+ };
+
+ static const u8 wlMapSetA[8] = {
+ 4, 6, 8, 10, 12, 14, 16, 18
+ };
+
+ u32 rlIndex = 0;
+ u32 wlIndex = 0;
+
+ for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
+ if (rlMapDBI[i] == RL) {
+ rlIndex = i;
+ break;
+ }
+ }
+
+ for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
+ if (wlMapSetA[i] == WL) {
+ wlIndex = i;
+ break;
+ }
+ }
+
+ /* DBI is always enabled. */
+ mrw2 = static_cast(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
+ }
+
+ void CalculateTimings(double tCK_avg, u32 freq) {
+ RL = RL_1331;
+ WL = WL_1331;
+
+ HandleLatency(freq);
+
+ GetRext();
+
+ /* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */
+ /* This is a lazy workaround until I find the issue... */
+ const bool lowFreq = freq < C.timingEmcTbreak;
+
+ tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
+ tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
+ tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
+ tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
+ tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
+
+ u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
+ u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
+
+ s32 finetRTW = C.fineTune_t6_tRTW;
+ s32 finetWTR = C.fineTune_t7_tWTR;
+
+ u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
+ refresh_raw = 0xFFFF;
+ if (tREFI != 6) {
+ refresh_raw = CEIL(tREFpb_values[tREFI] / tCK_avg) - 0x40;
+ refresh_raw = MIN(refresh_raw, static_cast(0xFFFF));
+ }
+
+ tRC = tRAS + tRPpb;
+ tRFCab = tRFCpb * 2;
+ tXSR = static_cast(tRFCab + 7.5);
+ tFAW = static_cast(tRRD * 4.0);
+ tRPab = tRPpb + 3;
+
+ tR2P = CEIL((RL * 0.426) - 2.0);
+ tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW;
+ tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
+ tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
+
+ rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017);
+ qpop = rdv - 14;
+ quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
+ quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg);
+ einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
+ einput = quse - CEIL(9.928 / tCK_avg);
+ u32 qrst_duration = FLOOR(8.399 - tCK_avg);
+ u32 qrstLow = MAX(static_cast(einput - qrst_duration - 2), static_cast(0));
+ qrst = PACK_U32(qrst_duration, qrstLow);
+ ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
+ qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
+ tW2P = (CEIL(WL * 1.7303) * 2) - 5;
+ tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast(tW2P))) + (BL / 2));
+ tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
+ tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
+ tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
+
+ wdv = WL;
+ wsv = WL - 2;
+ wev = 0xA + (WL - 14);
+
+ u32 obdlyHigh = 3 / FLOOR(MIN(static_cast(2), tCK_avg * (WL - 7)));
+ u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
+ obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
+
+ pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
+
+ tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
+
+ u32 tMMRI = tRCD + (tCK_avg * 3);
+ pdex2mrr = tMMRI + 10;
+
+ CalculateMrw2();
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp
similarity index 96%
rename from Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.hpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp
index 84165e07..8659d3f2 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/calculate_timings.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/calculate_timings_mariko.hpp
@@ -1,24 +1,24 @@
-/*
- * Copyright (c) Lightos_
- *
- * 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.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#pragma once
-
-namespace ams::ldr::hoc::pcv::mariko {
-
- void CalculateTimings(double tCK_avg, u32 freq);
-
-}
-
+/*
+ * Copyright (c) Lightos_
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ void CalculateTimings(double tCK_avg, u32 freq);
+
+}
+
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp
new file mode 100644
index 00000000..3525a5a3
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.cpp
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include "../pcv.hpp"
+#include "../../mtc_timing_value.hpp"
+#include "pcv_mariko.hpp"
+#include "pcv_mariko_cpu.hpp"
+#include "pcv_mariko_gpu.hpp"
+#include "pcv_mariko_mtc.hpp"
+#include "calculate_timings_mariko.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ u32 *nsoStart;
+
+ namespace {
+ size_t g_nso_size = 0;
+ uintptr_t g_cave_cursor = 0;
+ }
+
+ static uintptr_t CaveReserve(size_t count) {
+ if (g_pcv_cave == 0 || g_cave_cursor == 0) {
+ return 0;
+ }
+ if (g_cave_cursor + count * sizeof(u32) > g_pcv_cave + g_pcv_cave_size) {
+ return 0;
+ }
+ const uintptr_t entry = g_cave_cursor;
+ g_cave_cursor += count * sizeof(u32);
+ return entry;
+ }
+
+ #if HOC_UART_LOG
+ /* Redirect pcv's NvLog() calls to UART */
+ Result NvLogUartRedirect(u32 *ptr) {
+ const uintptr_t mapped_nso = reinterpret_cast(nsoStart);
+ const size_t nso_size = g_nso_size;
+ const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */
+ const uintptr_t vsnprintf_addr = reinterpret_cast(ptr);
+
+ /* NvLog via the VDD_SOC log */
+ static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n";
+ constexpr size_t FmtLen = sizeof(Fmt) - 1;
+ uintptr_t strAddr = 0;
+ {
+ const char *hay = reinterpret_cast(mapped_nso);
+ for (size_t i = 0; i + FmtLen <= nso_size; ++i) {
+ if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; }
+ }
+ }
+ if (strAddr == 0) {
+ LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso);
+ R_THROW(ldr::ResultInvalidNvLogRedirect());
+ }
+
+ uintptr_t nvlog_addr = 0;
+ for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) {
+ const uintptr_t pc = reinterpret_cast(p);
+ if (!AsmIsAdrp(p[0])) {
+ continue;
+ }
+ const uintptr_t adrpPage = (pc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(p[0]));
+ if (adrpPage != (strAddr & ~static_cast(0xFFFu))) {
+ continue;
+ }
+ const u32 reg = asm_get_rd(p[0]);
+ if (!(AsmIsAddImm64(p[1]) && asm_get_rd(p[1]) == reg && AsmGetRn(p[1]) == reg && AsmGetImm12(p[1]) == (strAddr & 0xFFFu))) {
+ continue;
+ }
+ for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) {
+ if (AsmIsBl(p[k])) { nvlog_addr = AsmBranchTarget(p[k], pc + k * 4); break; }
+ }
+ if (nvlog_addr != 0) {
+ break;
+ }
+ }
+ if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) {
+ LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso);
+ R_THROW(ldr::ResultInvalidNvLogRedirect());
+ }
+
+ const uintptr_t helper = CaveReserve(40);
+ if (helper == 0) {
+ LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)",
+ static_cast(g_pcv_cave), static_cast(g_pcv_cave_size));
+ R_THROW(ldr::ResultInvalidNvLogRedirect());
+ }
+ u32 *t = reinterpret_cast(helper);
+ size_t n = 0;
+ auto emit = [&](u32 ins) { t[n] = ins; ++n; };
+
+ emit(AsmMakeSubImm64(31, 31, 0x200));
+ emit(AsmMakeStpImm64(0, 1, 31, 0x100));
+ emit(AsmMakeStpImm64(2, 3, 31, 0x110));
+ emit(AsmMakeStpImm64(4, 5, 31, 0x120));
+ emit(AsmMakeStpImm64(6, 7, 31, 0x130));
+ emit(AsmMakeStpqImm(0, 1, 31, 0x140));
+ emit(AsmMakeStpqImm(2, 3, 31, 0x160));
+ emit(AsmMakeStpqImm(4, 5, 31, 0x180));
+ emit(AsmMakeStpqImm(6, 7, 31, 0x1A0));
+ emit(AsmMakeStrImm64(30, 31, 0x1E0));
+ emit(AsmMakeAddImm64(9, 31, 0x200)); emit(AsmMakeStrImm64(9, 31, 0x1C0)); /* __stack */
+ emit(AsmMakeAddImm64(9, 31, 0x140)); emit(AsmMakeStrImm64(9, 31, 0x1C8)); /* __gr_top */
+ emit(AsmMakeAddImm64(9, 31, 0x1C0)); emit(AsmMakeStrImm64(9, 31, 0x1D0)); /* __vr_top */
+ emit(AsmMakeMovnW(9, 0x37)); emit(AsmMakeStrImm32(9, 31, 0x1D8)); /* __gr_offs = -56 */
+ emit(AsmMakeMovnW(9, 0x7F)); emit(AsmMakeStrImm32(9, 31, 0x1DC)); /* __vr_offs = -128 */
+ emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (buf) */
+ emit(AsmMakeMovzW(1, 0x100)); /* size = 0x100 */
+ emit(AsmMakeLdrImm64(2, 31, 0x100)); /* fmt (saved x0) */
+ emit(AsmMakeAddImm64(3, 31, 0x1C0)); /* ap */
+ emit(AsmMakeBl(helper + n * 4, vsnprintf_addr));
+ emit(AsmMakeMovReg(1, 0)); /* len = retval */
+ emit(AsmMakeCmpImm32(1, 0x100));
+ { const size_t at = n; emit(AsmMakeBCond(helper + at * 4, helper + (at + 2) * 4, 0x3u)); } /* b.lo +2 */
+ emit(AsmMakeMovzW(1, 0xFF)); /* clamp len */
+ emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (str) */
+ emit(AsmMakeSvc(0x27)); /* svcOutputDebugString */
+ emit(AsmMakeLdrImm64(30, 31, 0x1E0));
+ emit(AsmMakeAddImm64(31, 31, 0x200));
+ emit(RetIns);
+
+ /* Redirect the call sites as patching the actual function causes crash */
+ const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */
+ size_t patchedSites = 0;
+ if (HOC_PCV_NVLOG_PATCH) {
+ for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) {
+ if (!AsmIsBl(*p)) {
+ continue;
+ }
+ const uintptr_t pc = reinterpret_cast(p);
+ if (AsmBranchTarget(*p, pc) != nvlog_addr) {
+ continue;
+ }
+ bool isFmtCall = false;
+ for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) {
+ const u32 w = *(p - j);
+ if (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0, */
+ const uintptr_t wpc = pc - j * 4;
+ const uintptr_t tgtPage = (wpc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(w));
+ if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; }
+ }
+ }
+ if (isFmtCall) {
+ PATCH_OFFSET(p, AsmMakeBl(pc, helper));
+ ++patchedSites;
+ }
+ }
+ }
+
+ LOGGING("NvLogRedirect: stub@+%lx vsnprintf@+%lx helper@+%lx instr=%zu sites=%zu",
+ nvlog_addr - mapped_nso, vsnprintf_addr - mapped_nso, helper - mapped_nso, n, patchedSites);
+ R_SUCCEED();
+ }
+ #endif
+
+ /* Relocate C2/C3Bus to avoid issues*/
+ Result BusFreqReloc(u32 *ptr) {
+ const u32 busReg = AsmGetRn(ptr[0]); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */
+ const u32 bufReg = asm_get_rd(ptr[0]); /* : freq-buffer arg */
+ const u32 bufOff = AsmGetLdStImm64Off(ptr[0]); /* : bus->freqBuf offset */
+ const u32 cntReg = asm_get_rd(ptr[1]); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */
+ const u32 railReg = asm_get_rd(ptr[2]); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */
+ u32 *call = ptr + 3; /* the bl to relocate */
+ const uintptr_t realFn = AsmBranchTarget(*call, reinterpret_cast(call));
+
+ /* Pick 3 scratch registers */
+ u32 s[3], sc = 0;
+ for (u32 r = 9; r <= 15 && sc < 3; ++r) {
+ if (r != busReg && r != bufReg && r != cntReg && r != railReg) {
+ s[sc++] = r;
+ }
+ }
+ R_UNLESS(sc == 3, ldr::ResultInvalidBusFreqReloc());
+
+ const uintptr_t tramp = CaveReserve(9);
+ R_UNLESS(tramp != 0, ldr::ResultInvalidBusFreqReloc());
+
+ const uintptr_t region = g_pcv_scratch + HocBusFreqBufOffset; /* [0]=counter, +0x10 + i*0x400 = bufs */
+ u32 *t = reinterpret_cast(tramp);
+ size_t n = 0;
+ auto emit = [&](u32 ins) { t[n] = ins; ++n; };
+ emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0, */
+ emit(AsmMakeAddImm64(s[0], s[0], region & 0xFFFu)); /* add s0,s0,#lo */
+ emit(AsmMakeLdrImm32(s[1], s[0], 0x00)); /* s1 = counter */
+ emit(AsmMakeAddImm64(s[2], s[1], 1)); /* s2 = counter+1 */
+ emit(AsmMakeStrImm32(s[2], s[0], 0x00)); /* counter++ */
+ emit(AsmMakeAddImm64(s[0], s[0], 0x10)); /* s0 = region+0x10 (buffers) */
+ emit(AsmMakeAddShiftedReg64(bufReg, s[0], s[1], 10)); /* Xbuf = s0 + counter*0x400 */
+ emit(AsmMakeStrImm64(bufReg, busReg, bufOff)); /* bus[freqBuf] = Xbuf */
+ emit(AsmMakeB(tramp + n * 4, realFn)); /* tail-call the real function */
+
+ PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast(call), tramp));
+ const uintptr_t base = reinterpret_cast(nsoStart);
+ (void) base;
+ LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)",
+ reinterpret_cast(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]);
+ R_SUCCEED();
+ }
+
+
+ #if HOC_UART_LOG
+ /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */
+ Result ForceVerbosity(u32 *ptr) {
+ PATCH_OFFSET(&ptr[0], AsmMakeMovzW(0, static_cast(HOC_PCV_FORCE_VERBOSITY))); /* movz w0,#level */
+ PATCH_OFFSET(&ptr[1], RetIns); /* ret */
+ R_SUCCEED();
+ }
+ #endif
+
+ /* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */
+ Result EmcSocLutReloc(u32 *ptr) {
+ constexpr u32 Window = 48;
+
+ u32 *freqStore = ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, asm_compare_no_rd); /* str x?,[x8,#0x18] */
+ u32 *voltStore = ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, asm_compare_no_rd); /* str w?,[x8,#0x48] */
+ u32 *readLoad = ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, asm_compare_no_rd); /* ldr w?,[x9,#0x48] */
+ R_UNLESS(freqStore && voltStore && readLoad, ldr::ResultInvalidEmcSocLut());
+
+ u32 *voltBase = voltStore - 2; /* `add Xb,Xsrc,Xi,LSL#2` (a cmn sits between it and store) */
+ R_UNLESS(AsmIsAddShiftedReg64(*voltBase) && asm_get_rd(*voltBase) == AsmGetRn(*voltStore),
+ ldr::ResultInvalidEmcSocLut());
+
+ /* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */
+ const u32 lutReg = asm_get_rd(ptr[0]);
+ const u32 railReg = AsmGetRn(ptr[0]);
+ R_UNLESS(AsmIsAdrp(ptr[-3]) && asm_get_rd(ptr[-3]) == lutReg, ldr::ResultInvalidEmcSocLut());
+ R_UNLESS(AsmIsAddImm64(ptr[-2]) && asm_get_rd(ptr[-2]) == lutReg && AsmGetRn(ptr[-2]) == lutReg,
+ ldr::ResultInvalidEmcSocLut());
+
+ const u32 srcBase = AsmGetRn(*voltBase); /* rail ptr at +0x20 */
+ const u32 wBase = asm_get_rd(*voltBase); /* base reg */
+ const u32 wIdx = AsmGetRm(*voltBase); /* loop index */
+
+ PATCH_OFFSET(freqStore, NopIns); /* Unneeded */
+ PATCH_OFFSET(voltBase, AsmMakeLdrImm64(wBase, srcBase, 0x20)); /* ldr Xb,[Xsrc,#0x20] (rail) */
+ PATCH_OFFSET(voltStore - 1, AsmMakeAddImm64(wBase, wBase, 0x18)); /* add Xb,Xb,#0x18 (was cmn) */
+ PATCH_OFFSET(voltStore, AsmSetLdStRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
+ PATCH_OFFSET(voltStore + 1, NopIns); /* Unneeded */
+
+ /* rail+0x18 as the socMinLut pointer. */
+ PATCH_OFFSET(ptr - 3, NopIns);
+ PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 0x18));/* add Xl,rail,#0x18 */
+
+ /* Drop the abort branch in case of a bad read */
+ for (u32 i = 1; i <= 4; ++i) {
+ if (AsmIsBCond(readLoad[i])) {
+ PATCH_OFFSET(&readLoad[i], NopIns);
+ break;
+ }
+ }
+ R_SUCCEED();
+ }
+
+ Result EmcDvfsCountLimit(u32 *ptr) {
+ R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount());
+
+ /* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
+ PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount)));
+ R_SUCCEED();
+ }
+
+ Result EmcRateListLimit(u32 *ptr) {
+ /* ptr = cmp w?,#0x20 ; ptr[1] = csel w?,w?,w?,lt (w? = min(maxCount, 32)) ; ptr[2] = bl */
+ R_UNLESS(EmcRateListPatternFn(ptr), ldr::ResultInvalidEmcRateList());
+
+ /* The csel's Rm holds the 32 cap. */
+ const u32 capReg = AsmGetRm(ptr[1]);
+ const u32 capMov = AsmMakeMovzW(capReg, 0x20); /* movz w,#0x20 */
+
+ u32 *movPtr = nullptr;
+ for (u32 i = 1; i <= 16; ++i) {
+ if (*(ptr - i) == capMov) {
+ movPtr = ptr - i;
+ break;
+ }
+ }
+ R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
+
+ /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
+ PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
+ PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */
+ R_SUCCEED();
+ }
+
+ Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) {
+ struct {
+ u8 reg;
+ u8 val;
+ } __attribute__((packed)) cmd;
+
+ I2cSession _session;
+ R_TRY(i2cOpenSession(&_session, dev));
+
+ cmd.reg = reg;
+ cmd.val = val;
+ Result 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();
+ Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
+ i2cExit();
+
+ R_SUCCEED();
+
+ return resultI2C;
+ }
+
+ Result GetSocSpeedo(u32 &socSpeedo) {
+ constexpr u64 FusePhysicalAddress = 0x7000F000;
+ u64 virtualAddress = 0;
+ constexpr u64 Size = 0x1000;
+
+ u64 outSize;
+ /* TODO: use svc::QueryMemoryMapping instead. */
+ R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
+
+ constexpr u32 FuseOffset = 2048;
+ constexpr u32 SocSpeedoOffset = 308;
+ socSpeedo = *reinterpret_cast(virtualAddress + FuseOffset + SocSpeedoOffset);
+
+ R_SUCCEED();
+ }
+
+ u32 GetSocProcessId(u32 socSpeedo) {
+ if (socSpeedo <= 1597) {
+ return 0;
+ }
+
+ if (socSpeedo <= 1708) {
+ return 1;
+ }
+
+ /* >= 1709. */
+ return 2;
+ }
+
+ Result SocVoltAsm(u32 *compareSpeedos) {
+ constexpr u32 VoltageScanLimit = 10;
+ /* Might actually be speedo id. */
+ u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd);
+ R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
+ u8 writeProcessIdRd = asm_get_rd(*writeProcessId);
+
+ /* This writes 1050mV. */
+ u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd);
+ R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
+ u8 writeVoltageRd = asm_get_rd(*writeVoltage);
+
+ /* A csel instruction is used to select the soc voltage limit register. */
+ /* We care about its destination register since that is used for verification. */
+ constexpr u32 VoltageSelectScanLimit = 24;
+ u32 *selectVoltage = ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, AsmCompareCselNoReg);
+ R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
+ /* Todo: check rm and rn? */
+ u8 selectVoltageRd = asm_get_rd(*selectVoltage);
+
+ /* rdCsel is then multiplied by 1000 to convert to uV. */
+ /* This is pretty far down the function. */
+ constexpr u32 MultiplierScanLimit = 200;
+ u32 *multiplier = ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, AsmCompareMullNoReg);
+ R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
+ u8 multiplierRn = AsmGetMullRn(*multiplier);
+ u8 multiplierRm = AsmGetMullRm(*multiplier);
+ /* One of the two registers has to be rdCsel. */
+ R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
+ u8 multiplierRd = asm_get_rd(*multiplier);
+
+ /* Subs instruction is then used to verify against absolute limit. */
+ u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd);
+ u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg);
+ R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
+
+ /* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
+ u32 *branchToAbort = limitValidation + 1;
+ R_UNLESS(AsmCompareBrConNoImm19(*branchToAbort, SocVoltBranchToAbortAsm), ldr::ResultInvalidSocVoltPattern());
+
+ if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
+ R_SKIP();
+ }
+
+ /* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
+ /* 2200 should be high enough :D */
+ u32 compareSpeedosPatch = AsmSubsSetImm12(*compareSpeedos, 2200);
+ PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
+
+ u32 socSpeedo = 0;
+ R_TRY(GetSocSpeedo(socSpeedo));
+
+ /* Adjust processId from 0 to [process id of switch booting this]. */
+ /* We're overwriting the orr instruction entirly. */
+ u32 processId = GetSocProcessId(socSpeedo);
+ u32 writeProcessIdPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd);
+ PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
+
+ /* Adjust voltage limit. */
+ u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd);
+ PATCH_OFFSET(writeVoltage, voltageLimitPatch);
+
+ /* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
+ PATCH_OFFSET(branchToAbort, NopIns);
+
+ R_SUCCEED();
+ }
+
+ Result SocVoltLimit(u32 *ptr) {
+ R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
+ if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
+ R_SKIP();
+ }
+
+ constexpr u32 Step = 25;
+ u32 maxVolt = C.marikoSocVmax;
+ if (maxVolt % Step) {
+ maxVolt = maxVolt / Step * Step; /* Round. */
+ }
+
+ u32 volt = SocVoltLimitOfficial;
+ for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
+ volt += Step;
+ PATCH_OFFSET(ptr + i, volt);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result EmcRateSessLimit(u32 *ptr) {
+ u32 movzI = 0;
+ R_UNLESS(EmcRateSessFindClamp(ptr, nullptr, nullptr, &movzI), ldr::ResultInvalidEmcRateList());
+
+ /* Reject cmd11 GetDvfsTable. */
+ for (u32 i = 1; i <= 24; ++i) {
+ const u32 w = ptr[i];
+ if (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */
+ R_THROW(ldr::ResultInvalidEmcRateList());
+ }
+ }
+
+ /* mov x,x2 */
+ u32 descReg = 0xFFu;
+ for (u32 i = 1; i <= 24; ++i) {
+ if (AsmIsMovReg(ptr[i], 2)) { descReg = asm_get_rd(ptr[i]); break; }
+ }
+ R_UNLESS(descReg != 0xFFu, ldr::ResultInvalidEmcRateList());
+
+ /* Repoint the duplicated-imm pair */
+ u32 *adds[8]; u32 addImm[8]; u32 nAdds = 0;
+ for (u32 i = 1; i <= 24 && nAdds < 8; ++i) {
+ const u32 w = ptr[i];
+ if (AsmIsAddSpImm(w)) { /* add x?,sp,#imm12 (shift 0) */
+ adds[nAdds] = ptr + i;
+ addImm[nAdds] = AsmGetImm12(w);
+ ++nAdds;
+ }
+ }
+ u32 patched = 0;
+ for (u32 a = 0; a < nAdds; ++a) {
+ bool dup = false;
+ for (u32 b = 0; b < nAdds; ++b) {
+ if (a != b && addImm[a] == addImm[b]) { dup = true; break; }
+ }
+ if (dup) {
+ PATCH_OFFSET(adds[a], AsmMakeLdrImm64(asm_get_rd(*adds[a]), descReg, 0)); /* ldr x?,[x] */
+ ++patched;
+ }
+ }
+ R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
+
+ /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
+ PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
+ PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */
+ R_SUCCEED();
+ }
+
+ void Patch(uintptr_t mapped_nso, size_t nso_size) {
+ nsoStart = reinterpret_cast(mapped_nso);
+
+ g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize;
+ g_nso_size = nso_size;
+ g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */
+
+ MtcGenerateFreqTables();
+
+ 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 DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial },
+ { "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
+ { "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
+ { "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
+ { "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
+ { "GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq },
+ { "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
+ { "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
+ { "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
+ { "MEM Freq Mtc", &MemFreqMtcTable, 1, 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 },
+ { "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn },
+ // { "EMC DVFS Count", &EmcDvfsCountLimit, 1, &EmcDvfsCountPatternFn },
+ //{ "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn },
+ // { "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn },
+ // { "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn },
+ // { "Bus Freq Reloc", &BusFreqReloc, 1, &BusFreqRelocPatternFn },
+ { "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
+ { "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
+ /* Debugging patches */
+ #if HOC_UART_LOG
+ { "NvLog Redirect", &NvLogUartRedirect, 1, &NvLogVsnprintfPatternFn, 0, 0, true },
+ { "Force Verbosity", &ForceVerbosity, 3, &ForceVerbosityPatternFn, 0, 0, true },
+ #endif
+ };
+
+ 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())) {
+ panic::SmcError(panic::Patch);
+
+ CRASH(entry.description);
+ }
+ }
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp
similarity index 99%
rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp
index de72f004..33b7ed09 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko.hpp
@@ -20,12 +20,14 @@
#pragma once
-#include "../oc_common.hpp"
-#include "pcv_common.hpp"
-#include "pcv_asm.hpp"
+#include "../../oc_common.hpp"
+#include "../pcv_common.hpp"
+#include "../pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::mariko {
+ extern u32 *nsoStart;
+
constexpr cvb_entry_t CpuCvbTableDefault[] = {
{ 204000, { 721589, -12695, 27 }, { } },
{ 306000, { 747134, -14195, 27 }, { } },
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp
new file mode 100644
index 00000000..01400c9d
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "../pcv.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ u32 CapCpuClock() {
+ u32 cpuCap = allowedCpuMaxFrequencies[0];
+
+ for (u32 freq : allowedCpuMaxFrequencies) {
+ if (C.marikoCpuMaxClock >= freq) {
+ cpuCap = freq;
+ } else {
+ break;
+ }
+ }
+ return cpuCap;
+ }
+
+ 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.marikoCpuUVHigh) {
+ PATCH_OFFSET(ptr, CapCpuClock());
+ } else {
+ PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result CpuVoltDVFS(u32 *ptr) {
+ CvbMeta *cpuCvbMeta = reinterpret_cast(reinterpret_cast(ptr) - offsetof(CvbMeta, vmin));
+
+ R_UNLESS(cpuCvbMeta->highVmin == CpuHighVminOfficial, ldr::ResultInvalidCpuMinVolt());
+ R_UNLESS(cpuCvbMeta->unkStepMaybe == 38, ldr::ResultInvalidCpuMinVolt());
+ R_UNLESS(cpuCvbMeta->vmax == CpuVoltOfficial, ldr::ResultInvalidCpuMinVolt());
+ R_UNLESS(cpuCvbMeta->unkScale2 == 1000, ldr::ResultInvalidCpuMinVolt());
+ R_UNLESS(cpuCvbMeta->speedoScale == 100, ldr::ResultInvalidCpuMinVolt());
+ R_UNLESS(cpuCvbMeta->voltageScale == 1000, ldr::ResultInvalidCpuMinVolt());
+ R_UNLESS(cpuCvbMeta->unkZero5 == 0, ldr::ResultInvalidCpuMinVolt());
+
+ if (C.marikoCpuLowVmin) {
+ PATCH_OFFSET(&(cpuCvbMeta->vmin), C.marikoCpuLowVmin);
+ }
+
+ if (C.marikoCpuHighVmin) {
+ PATCH_OFFSET(&(cpuCvbMeta->highVmin), C.marikoCpuHighVmin);
+ }
+
+ if (C.marikoCpuMaxVolt) {
+ PATCH_OFFSET(&(cpuCvbMeta->vmax), C.marikoCpuMaxVolt);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result CpuVoltThermals(u32 *ptr) {
+ if (std::memcmp(ptr, cpuVoltThermalData, sizeof(cpuVoltThermalData))) {
+ R_THROW(ldr::ResultInvalidCpuMinVolt());
+ }
+
+ if (C.marikoCpuLowVmin) {
+ PATCH_OFFSET(ptr, C.marikoCpuLowVmin);
+ PATCH_OFFSET(ptr + 3, C.marikoCpuLowVmin);
+ }
+
+ if (C.marikoCpuMaxVolt) {
+ PATCH_OFFSET(ptr - 2, C.marikoCpuMaxVolt);
+ PATCH_OFFSET(ptr - 5, C.marikoCpuMaxVolt);
+ PATCH_OFFSET(ptr + 1, C.marikoCpuMaxVolt);
+ PATCH_OFFSET(ptr + 4, C.marikoCpuMaxVolt);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result CpuVoltDfll(u32 *ptr) {
+ CvbCpuDfllData *entry = reinterpret_cast(ptr);
+
+ R_UNLESS(entry->tune0_low == 0xFFCF, ldr::ResultInvalidCpuVoltDfllEntry());
+ R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
+ R_UNLESS(entry->tune1_low == 0x12207FF, ldr::ResultInvalidCpuVoltDfllEntry());
+ R_UNLESS(entry->tune1_high == 0x3FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry());
+
+ switch (C.marikoCpuUVLow) {
+ case 1:
+ PATCH_OFFSET(&(entry->tune0_low), 0xffa0);
+ PATCH_OFFSET(&(entry->tune0_high), 0xffff);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x0);
+ break;
+ case 2:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
+ break;
+ case 3:
+ PATCH_OFFSET(&(entry->tune0_low), 0xffdf);
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
+ break;
+ case 4:
+ PATCH_OFFSET(&(entry->tune0_low), 0xffff);
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
+ break;
+ case 5:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
+ break;
+ case 6:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
+ break;
+ case 7:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
+ break;
+ case 8:
+ PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
+ break;
+ case 9:
+ PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
+ break;
+ case 10:
+ PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
+ break;
+ case 11:
+ PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
+ break;
+ case 12:
+ PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
+ break;
+ default:
+ break;
+ }
+
+ switch (C.marikoCpuUVHigh) {
+ case 1:
+ PATCH_OFFSET(&(entry->tune1_high), 0x0);
+ PATCH_OFFSET(&(entry->tune0_high), 0xffff);
+ break;
+ case 2:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
+ break;
+ case 3:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
+ break;
+ case 4:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
+ break;
+ case 5:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
+ break;
+ case 6:
+ PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
+ break;
+ case 7:
+ case 8:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
+ break;
+ case 9:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
+ break;
+ case 10:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
+ break;
+ case 11:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
+ break;
+ case 12:
+ PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
+ PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
+ break;
+ default:
+ break;
+ }
+
+ R_SUCCEED();
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp
new file mode 100644
index 00000000..f6a570d4
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_cpu.hpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "../pcv.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ Result CpuFreqVdd(u32 *ptr);
+ Result CpuVoltDVFS(u32 *ptr);
+ Result CpuVoltThermals(u32 *ptr);
+ Result CpuVoltDfll(u32 *ptr);
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp
new file mode 100644
index 00000000..5b6c1286
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "../pcv.hpp"
+#include "../pcv_asm.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ Result GpuVoltDVFS(u32 *ptr) {
+ /* Check for valid pattern. */
+ for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) {
+ if (*(ptr + i + 1) != gpuDVFSPattern[i]) {
+ R_THROW(ldr::ResultInvalidGpuDvfs());
+ }
+ }
+
+ /* Default value is 1050mV. */
+ if (C.marikoGpuVmax) {
+ PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
+ }
+
+ if (C.marikoGpuVmin) {
+ PATCH_OFFSET(ptr, C.marikoGpuVmin);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result GpuVoltThermals(u32 *ptr) {
+ if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
+ R_THROW(ldr::ResultInvalidGpuDvfs());
+ }
+
+ // if (C.marikoGpuBootVolt) {
+ // PATCH_OFFSET(ptr - 3, C.marikoGpuBootVolt);
+ // }
+
+ if (C.marikoGpuVmin) {
+ PATCH_OFFSET(ptr, C.marikoGpuVmin);
+ PATCH_OFFSET(ptr + 3, C.marikoGpuVmin);
+ PATCH_OFFSET(ptr + 6, C.marikoGpuVmin);
+ PATCH_OFFSET(ptr + 9, C.marikoGpuVmin);
+ PATCH_OFFSET(ptr + 12, C.marikoGpuVmin);
+ }
+
+ 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, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[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;
+ case 3:
+ max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT15)->freq;
+ break;
+ case 4:
+ max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHighUV)->freq;
+ break;
+ default:
+ max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
+ break;
+ }
+
+ u32 asm_patch[2] = {
+ asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
+ asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
+ };
+
+ PATCH_OFFSET(ptr32, asm_patch[0]);
+ PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
+
+ R_SUCCEED();
+ }
+
+ Result GpuFreqPllMax(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();
+ }
+
+ Result GpuFreqPllLimit(u32 *ptr) {
+ u32 prev_freq = *(ptr - 1);
+
+ if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
+ R_THROW(ldr::ResultInvalidGpuPllEntry());
+ }
+
+ PATCH_OFFSET(ptr, 3600000);
+
+ R_SUCCEED();
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp
new file mode 100644
index 00000000..f5e4c33f
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_gpu.hpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "../pcv.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ Result GpuVoltDVFS(u32 *ptr);
+ Result GpuVoltThermals(u32 *ptr);
+ Result GpuFreqMaxAsm(u32 *ptr32);
+ Result GpuFreqPllMax(u32 *ptr);
+ Result GpuFreqPllLimit(u32 *ptr);
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp
new file mode 100644
index 00000000..7dacbdbe
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.cpp
@@ -0,0 +1,676 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "../pcv.hpp"
+#include "../../mtc_timing_value.hpp"
+#include "calculate_timings_mariko.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ namespace {
+ std::vector newEmcList;
+ }
+
+ 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.1: MC Registers
+ * Section 18.11.2: EMC Registers
+ */
+
+ #define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
+ TABLE->burst_regs.PARAM = VALUE; \
+ TABLE->shadow_regs_ca_train.PARAM = VALUE; \
+ TABLE->shadow_regs_rdwr_train.PARAM = VALUE;
+
+ const double tCK_avg = 1000'000.0 / table->rate_khz;
+
+ #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
+
+ /* Ram power down */
+ /* B31: DRAM_CLKSTOP_PD */
+ /* B30: DRAM_CLKSTOP_SR */
+ /* B29: DRAM_ACPD */
+ if (C.hpMode) {
+ WRITE_PARAM_ALL_REG(table, emc_cfg, 0x13200000);
+ } else {
+ WRITE_PARAM_ALL_REG(table, emc_cfg, 0xF3200000);
+ }
+
+ u32 trefbw = refresh_raw + 0x40;
+ trefbw = MIN(trefbw, static_cast(0x3FFF));
+
+ const u32 dyn_self_ref_control = (static_cast(7605.0 / tCK_avg) + 260) | (table->burst_regs.emc_dyn_self_ref_control & 0xffff0000);
+
+ CalculateTimings(tCK_avg, table->rate_khz);
+
+ 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_rc, MIN(GET_CYCLE_CEIL(tRC), static_cast(0xB9)));
+ WRITE_PARAM_ALL_REG(table, emc_ras, MIN(GET_CYCLE_CEIL(tRAS), static_cast(0x7F)));
+ WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD));
+ WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb));
+ WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab));
+ WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb));
+ WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(tXSR), static_cast(0x3fe)));
+ WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(tXSR), static_cast(0x3fe)));
+ WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW));
+ WRITE_PARAM_ALL_REG(table, emc_trpab, MIN(GET_CYCLE_CEIL(tRPab), static_cast(0x3F)));
+ WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR));
+ WRITE_PARAM_ALL_REG(table, emc_tcke, GET_CYCLE_CEIL(7.425) + 2);
+ WRITE_PARAM_ALL_REG(table, emc_tpd, GET_CYCLE_CEIL(tXP));
+ WRITE_PARAM_ALL_REG(table, emc_tclkstop, tCLKSTOP);
+ WRITE_PARAM_ALL_REG(table, emc_r2p, tR2P);
+ WRITE_PARAM_ALL_REG(table, emc_r2w, tR2W);
+ WRITE_PARAM_ALL_REG(table, emc_trtm, tRTM);
+ WRITE_PARAM_ALL_REG(table, emc_tratm, tRATM);
+ WRITE_PARAM_ALL_REG(table, emc_w2p, tW2P);
+ WRITE_PARAM_ALL_REG(table, emc_w2r, tW2R);
+ WRITE_PARAM_ALL_REG(table, emc_twtm, tWTM);
+ WRITE_PARAM_ALL_REG(table, emc_twatm, tWATM);
+ WRITE_PARAM_ALL_REG(table, emc_rext, rext);
+ WRITE_PARAM_ALL_REG(table, emc_wext, (table->rate_khz >= 2533000) ? 0x19 : 0x16);
+ WRITE_PARAM_ALL_REG(table, emc_refresh, refresh_raw);
+ WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, refresh_raw / 4);
+ WRITE_PARAM_ALL_REG(table, emc_trefbw, trefbw);
+ WRITE_PARAM_ALL_REG(table, emc_dyn_self_ref_control, dyn_self_ref_control);
+ WRITE_PARAM_ALL_REG(table, emc_pdex2wr, pdex2rw);
+ WRITE_PARAM_ALL_REG(table, emc_pdex2rd, pdex2rw);
+ WRITE_PARAM_ALL_REG(table, emc_pchg2pden, GET_CYCLE_CEIL(1.763));
+ WRITE_PARAM_ALL_REG(table, emc_ar2pden, GET_CYCLE_CEIL(1.75));
+ WRITE_PARAM_ALL_REG(table, emc_pdex2cke, GET_CYCLE_CEIL(1.05));
+ WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(14.0));
+ WRITE_PARAM_ALL_REG(table, emc_cke2pden, GET_CYCLE_CEIL(8.499));
+ WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(pdex2mrr));
+ WRITE_PARAM_ALL_REG(table, emc_rw2pden, tWTPDEN);
+ WRITE_PARAM_ALL_REG(table, emc_einput, einput);
+ WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
+ WRITE_PARAM_ALL_REG(table, emc_obdly, obdly);
+ WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly);
+ WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv);
+ WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
+ WRITE_PARAM_ALL_REG(table, emc_quse, quse);
+ WRITE_PARAM_ALL_REG(table, emc_wdv, wdv);
+ WRITE_PARAM_ALL_REG(table, emc_wsv, wsv);
+ WRITE_PARAM_ALL_REG(table, emc_wev, wev);
+ WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
+ WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
+ WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
+ WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
+ WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
+ WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
+ WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
+ WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
+ WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
+ WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
+ WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
+ WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
+ WRITE_PARAM_ALL_REG(table, emc_cmd_brlshft_2, 0x24);
+ WRITE_PARAM_ALL_REG(table, emc_cmd_brlshft_3, 0x24);
+
+ /* This needs some clean up. */
+ constexpr double MC_ARB_DIV = 4.0;
+ constexpr u32 MC_ARB_SFA = 2;
+
+ table->burst_mc_regs.mc_emem_arb_cfg = table->rate_khz / (33.3 * 1000) / MC_ARB_DIV;
+ table->burst_mc_regs.mc_emem_arb_timing_rcd = CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2;
+ table->burst_mc_regs.mc_emem_arb_timing_rp = CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1;
+ table->burst_mc_regs.mc_emem_arb_timing_rc = CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1;
+ table->burst_mc_regs.mc_emem_arb_timing_ras = CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2;
+ table->burst_mc_regs.mc_emem_arb_timing_faw = CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1;
+ table->burst_mc_regs.mc_emem_arb_timing_rrd = CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1;
+ table->burst_mc_regs.mc_emem_arb_timing_rfcpb = CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV) - 1;
+ table->burst_mc_regs.mc_emem_arb_timing_rap2pre = CEIL(tR2P / MC_ARB_DIV);
+ table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(tW2P / MC_ARB_DIV) + MC_ARB_SFA;
+
+ /* Two consecutive reads between two different dram modules. */
+ /* Only above 1 for 8gb ram. */
+ if (table->burst_mc_regs.mc_emem_arb_timing_r2r > 1) {
+ table->burst_mc_regs.mc_emem_arb_timing_r2r = CEIL(table->burst_regs.emc_rext / 4) - 1 + MC_ARB_SFA;
+ }
+
+ /* Same as r2r but for write. */
+ if (table->burst_mc_regs.mc_emem_arb_timing_w2w > 1) {
+ table->burst_mc_regs.mc_emem_arb_timing_w2w = CEIL(table->burst_regs.emc_wext / MC_ARB_DIV) - 1 + MC_ARB_SFA;
+ }
+
+ table->burst_mc_regs.mc_emem_arb_timing_r2w = CEIL(tR2W / MC_ARB_DIV) - 1 + MC_ARB_SFA;
+ table->burst_mc_regs.mc_emem_arb_timing_w2r = CEIL(tW2R / MC_ARB_DIV) - 1 + MC_ARB_SFA;
+
+ u32 da_turns = 0;
+ da_turns |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16;
+ da_turns |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24;
+ table->burst_mc_regs.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 |= (table->burst_mc_regs.mc_emem_arb_timing_rc / 2);
+ da_covers |= (r_cover << 8);
+ da_covers |= (w_cover << 16);
+ table->burst_mc_regs.mc_emem_arb_da_covers = da_covers;
+
+ constexpr u32 AtomsPerDvfsPulse = 0x7;
+ constexpr u32 McEmcSameFreq = 0x0;
+ const u32 expiringSoonSlackThreshold = [&] {
+ switch (table->rate_khz) {
+ case 2966000:
+ case 3100000:
+ case 3133000:
+ case 3200000:
+ return 0x12u;
+ default:
+ return 0x13u;
+ }
+ }();
+
+ const u32 priorityInversionIsoThreshold = GET_CYCLE_CEIL(7.5);
+ constexpr u32 EmcReqB2bXfer = 0x0;
+ const u32 priorityInversionThreshold = GET_CYCLE_CEIL(22.5);
+ const u32 bc2aaHoldoffThreshold = table->burst_mc_regs.mc_emem_arb_timing_rc + 1;
+
+ const u32 mc_emem_arb_misc0 = (AtomsPerDvfsPulse << 28) | (McEmcSameFreq << 27) | (expiringSoonSlackThreshold << 21) | (priorityInversionIsoThreshold << 16) | (EmcReqB2bXfer << 15) | (priorityInversionThreshold << 8) | (bc2aaHoldoffThreshold << 0);
+ table->burst_mc_regs.mc_emem_arb_misc0 = mc_emem_arb_misc0;
+
+ table->la_scale_regs.mc_mll_mpcorer_ptsa_rate = 0x115;
+
+ if (table->rate_khz >= 2133000) {
+ table->la_scale_regs.mc_ftop_ptsa_rate = 0x1F;
+ } else {
+ table->la_scale_regs.mc_ftop_ptsa_rate = 0x1B;
+ }
+
+ table->la_scale_regs.mc_ptsa_grant_decrement = 0x17ff;
+
+ constexpr u32 MaskHigh = 0xFF00FFFF;
+ constexpr u32 Mask2 = 0xFFFFFF00;
+ constexpr u32 Mask3 = 0xFF00FF00;
+
+ const u32 allowance1 = static_cast(0x32000 / (table->rate_khz / 1000)) & 0xFF;
+ const u32 allowance2 = static_cast(0x9C40 / (table->rate_khz / 1000)) & 0xFF;
+ const u32 allowance3 = static_cast(0xB540 / (table->rate_khz / 1000)) & 0xFF;
+ const u32 allowance4 = static_cast(0x9600 / (table->rate_khz / 1000)) & 0xFF;
+ const u32 allowance5 = static_cast(0x8980 / (table->rate_khz / 1000)) & 0xFF;
+
+ table->la_scale_regs.mc_latency_allowance_xusb_0 = (table->la_scale_regs.mc_latency_allowance_xusb_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_xusb_1 = (table->la_scale_regs.mc_latency_allowance_xusb_1 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_tsec_0 = (table->la_scale_regs.mc_latency_allowance_tsec_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_sdmmcaa_0 = (table->la_scale_regs.mc_latency_allowance_sdmmcaa_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_sdmmcab_0 = (table->la_scale_regs.mc_latency_allowance_sdmmcab_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_sdmmc_0 = (table->la_scale_regs.mc_latency_allowance_sdmmc_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_sdmmca_0 = (table->la_scale_regs.mc_latency_allowance_sdmmca_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_ppcs_1 = (table->la_scale_regs.mc_latency_allowance_ppcs_1 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_nvdec_0 = (table->la_scale_regs.mc_latency_allowance_nvdec_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_mpcore_0 = (table->la_scale_regs.mc_latency_allowance_mpcore_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_avpc_0 = (table->la_scale_regs.mc_latency_allowance_avpc_0 & MaskHigh) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_isp2_1 = allowance1 | (table->la_scale_regs.mc_latency_allowance_isp2_1 & Mask3) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_gpu_0 = allowance2 | (table->la_scale_regs.mc_latency_allowance_gpu_0 & Mask3) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_gpu2_0 = allowance2 | (table->la_scale_regs.mc_latency_allowance_gpu2_0 & Mask3) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_vic_0 = allowance3 | (table->la_scale_regs.mc_latency_allowance_vic_0 & Mask3) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_nvenc_0 = allowance4 | (table->la_scale_regs.mc_latency_allowance_nvenc_0 & Mask3) | (allowance1 << 16);
+ table->la_scale_regs.mc_latency_allowance_hc_0 = (table->la_scale_regs.mc_latency_allowance_hc_0 & Mask2) | allowance5;
+ table->la_scale_regs.mc_latency_allowance_hc_1 = (table->la_scale_regs.mc_latency_allowance_hc_1 & Mask2) | allowance1;
+ table->la_scale_regs.mc_latency_allowance_vi2_0 = (table->la_scale_regs.mc_latency_allowance_vi2_0 & Mask2) | allowance1;
+
+ table->dram_timings.t_rp = tRP_values[0];
+ const u32 tRFCabStock = tRFC_values[0] * 2;
+ table->dram_timings.t_rfc = tRFCabStock;
+
+ table->dram_timings.rl = RL;
+ table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast(mrw2);
+ table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */
+ table->emc_cfg_2 = 0x11083D;
+ }
+
+ void MemMtcPllmbDivisor(MarikoMtcTable *table) {
+ constexpr u32 PllOscInKHz = 38400;
+ constexpr u32 PllOscHalfKHz = 19200;
+
+ double target_freq_d = static_cast(table->rate_khz);
+
+ s32 divm_candidate_half = static_cast(table->rate_khz / PllOscHalfKHz);
+
+ bool remainder_check = (table->rate_khz - PllOscInKHz * (table->rate_khz / PllOscInKHz)) > (table->rate_khz - PllOscHalfKHz * divm_candidate_half) && static_cast(((target_freq_d / PllOscHalfKHz - divm_candidate_half - 0.5) * 8192.0)) != 0;
+
+ u32 divm_final = remainder_check + 1;
+ table->pllmb_divm = divm_final;
+
+ double div_step_d = static_cast(PllOscInKHz) / divm_final;
+ s32 divn_integer = static_cast(table->rate_khz / div_step_d);
+ table->pllmb_divn = divn_integer;
+
+ u32 divn_fraction = static_cast((target_freq_d / div_step_d - divn_integer - 0.5) * 8192.0);
+
+ u32 actual_freq_khz = static_cast((divn_integer + 0.5 + divn_fraction * 0.000122070312) * div_step_d);
+
+ if (table->rate_khz - 2366001 < 133999) {
+ s32 divn_fraction_ssc = static_cast((actual_freq_khz * 0.997 / div_step_d - divn_integer - 0.5) * 8192.0);
+
+ double delta_scaled = (0.3 / div_step_d + 0.3 / div_step_d) * (divn_fraction - divn_fraction_ssc);
+ s32 delta_int = static_cast(delta_scaled);
+ double delta_frac = delta_scaled - delta_int;
+
+ u32 setup_value = 0;
+ if (delta_frac <= 0.5) {
+ double round_val = (delta_int + ROUND(delta_frac + delta_frac)) ? 0.5 : 0.0;
+ setup_value = ROUND(delta_frac + delta_frac) ? static_cast(round_val + round_val) | 0x1000 : static_cast(round_val);
+ } else {
+ s32 frac_doubled = ROUND(delta_frac - 0.5 + delta_frac - 0.5);
+ double round_val = 1.0;
+ setup_value = frac_doubled ? static_cast(round_val) : static_cast(round_val + round_val) | 0x1000;
+ }
+
+ u32 ctrl1 = static_cast(divn_fraction_ssc) | (static_cast(divn_fraction) << 16);
+ u32 ctrl2 = static_cast(divn_fraction) | (static_cast(setup_value) << 16);
+
+ table->pllm_ss_ctrl1 = ctrl1;
+ table->pllm_ss_ctrl2 = ctrl2;
+ table->pllmb_ss_ctrl1 = ctrl1;
+ table->pllmb_ss_ctrl2 = ctrl2;
+ } else {
+ table->pllm_ss_cfg &= 0xBFFFFFFF;
+ table->pllmb_ss_cfg &= 0xBFFFFFFF;
+
+ u64 pair = (static_cast(divn_fraction) << 32) | static_cast(table->rate_khz);
+ u32 pll_misc = (table->pllm_ss_ctrl2 & 0xFFFF0000) | static_cast((pair - actual_freq_khz) >> 32);
+
+ table->pllm_ss_ctrl2 = pll_misc;
+ table->pllmb_ss_ctrl2 = pll_misc;
+ }
+ }
+
+ void MtcGenerateJedecTable() {
+ const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
+ constexpr u32 JedecFreqCount = std::size(jedecFreqs);
+
+ for (u32 i = 0; i < JedecFreqCount; ++i) {
+ if (jedecFreqs[i] <= C.marikoEmcMaxClock) {
+ newEmcList.push_back(jedecFreqs[i]);
+ } else {
+ break;
+ }
+ }
+
+ if (newEmcList.back() != C.marikoEmcMaxClock) {
+ newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
+ }
+
+ newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
+ }
+
+ void MtcGenerate133StepTable() {
+ const u32 stepFreqs133[] = { 1733000, 1866000, 2000000, 2133000, 2266000, 2400000, 2533000, 2666000, 2800000, 2933000, 3066000, 3200000, 3333000, 3466000, }; // Avoid rounding issues
+ constexpr u32 StepFreqs133Size = std::size(stepFreqs133);
+
+ for (u32 i = 0; i < StepFreqs133Size; ++i) {
+ if (stepFreqs133[i] <= C.marikoEmcMaxClock) {
+ newEmcList.push_back(stepFreqs133[i]);
+ } else {
+ break;
+ }
+ }
+
+ if (newEmcList.back() != C.marikoEmcMaxClock) {
+ newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
+ }
+
+ newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
+ }
+
+ void MtcGenerate33StepTable() {
+ /* ~33.33MHz but rounded*/
+ const u32 stepFreqs33[] = {
+ 1633000, 1666000, 1700000, 1733000, 1766000, 1800000, 1833000, 1866000, 1900000, 1933000,
+ 1966000, 2000000, 2033000, 2066000, 2100000, 2133000, 2166000, 2200000, 2233000, 2266000,
+ 2300000, 2333000, 2366000, 2400000, 2433000, 2466000, 2500000, 2533000, 2566000, 2600000,
+ 2633000, 2666000, 2700000, 2733000, 2766000, 2800000, 2833000, 2866000, 2900000, 2933000,
+ 2966000, 3000000, 3033000, 3066000, 3100000, 3133000, 3166000, 3200000, 3233000, 3266000,
+ 3300000, 3333000, 3366000, 3400000, 3433000, 3466000, 3500000,
+ };
+ constexpr u32 StepFreqs33Size = std::size(stepFreqs33);
+
+ for (u32 i = 0; i < StepFreqs33Size; ++i) {
+ if (stepFreqs33[i] <= C.marikoEmcMaxClock) {
+ newEmcList.push_back(stepFreqs33[i]);
+ } else {
+ break;
+ }
+ }
+
+ if (newEmcList.back() != C.marikoEmcMaxClock) {
+ newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
+ }
+
+ newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
+ }
+
+ void MtcGenerateFreqTables() {
+ newEmcList.clear();
+ newEmcList.reserve(EmcDvfsTableEntryCount);
+ newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault));
+
+ if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
+ return;
+ }
+
+ u32 stepRate = 0;
+ switch (C.stepMode) {
+ case StepMode_33MHz:
+ MtcGenerate33StepTable();
+ return;
+ case StepMode_66MHz:
+ stepRate = 66667;
+ break;
+ case StepMode_100MHz:
+ stepRate = 100000;
+ break;
+ case StepMode_Jedec:
+ MtcGenerateJedecTable();
+ return;
+ case StepMode_133MHz:
+ MtcGenerate133StepTable();
+ return;
+ default:
+ stepRate = 66667;
+ break;
+ }
+
+ constexpr u32 RoundHz = 1000;
+ for (u32 stepIndex = 1;; ++stepIndex) {
+ u32 newFreq = EmcClkOSLimit + stepIndex * stepRate;
+ newFreq = (newFreq / RoundHz) * RoundHz;
+ if (newFreq > C.marikoEmcMaxClock) {
+ if (newEmcList.back() != C.marikoEmcMaxClock) {
+ newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
+ }
+ break;
+ }
+ newEmcList.push_back(newFreq);
+ }
+
+ newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
+ }
+
+ Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
+ R_UNLESS(tableStart->rate_khz == expectedFreq, ldr::ResultInvalidMtcTable());
+ R_UNLESS(tableStart->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable());
+
+ R_SUCCEED();
+ }
+
+ Result MtcValidateAllTables(MarikoMtcTable *tableStart, const u32 *validationList, u32 tableCount) {
+ for (u32 i = 0; i < tableCount; ++i) {
+ R_TRY(VerifyMtcTable(&tableStart[i], validationList[i]));
+ }
+
+ R_SUCCEED();
+ }
+
+ DramId GetDramId() {
+ u64 id64;
+ splGetConfig(SplConfigItem_DramId, &id64);
+ return static_cast(id64);
+ }
+
+ MtcTableIndex GetMtcDramIndex(DramId dramId) {
+ for (u32 i = 0; i < std::size(mtcIndexTable); ++i) {
+ if (mtcIndexTable[i].dramId == dramId) {
+ return mtcIndexTable[i].index;
+ }
+ }
+
+ return MtcTableIndex_Invalid;
+ }
+
+ NORETURN void AbortInvalidMtc(const char *crashMsg) {
+ panic::SmcError(panic::Emc);
+ CRASH(crashMsg);
+ }
+
+ u32 GetMtcOffset(MtcTableIndex index) {
+ if (index < T210b0SdevEmcDvfsTableS4gb03) {
+ return index * mariko::MtcFullTableSize;
+ }
+
+ /* There are 2 erista mtc tables between T210b0SdevEmcDvfsTableS4gb01 and T210b0SdevEmcDvfsTableS4gb03, so we have to do this adjustment. */
+ return mariko::MtcFullTableSize * index + (2 * erista::MtcFullTableSize);
+ }
+
+ void PrepareMtcMemoryRegion(u8 *firstTable, MarikoMtcTable *usedTable) {
+ memmove(firstTable, usedTable, mariko::MtcFullTableSize);
+
+ /* Clear all other tables. */
+ /* 1 erista table is excluded because it's always before firstTable. */
+ /* We also exclude the used table obviously. */
+ constexpr size_t RemainingRegionSize = (mariko::MtcFullTableSize) * (mariko::MtcFullTableCount - 1) + (erista::MtcFullTableSize * (erista::MtcFullTableCount - 1));
+ memset(firstTable + mariko::MtcFullTableSize, 0, RemainingRegionSize);
+ }
+
+ void MtcExtendTables(MarikoMtcTable *table) {
+ for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) {
+ std::memcpy(&table[i], &table[i - 1], sizeof(MarikoMtcTable));
+ table[i].rate_khz = newEmcList[i];
+ }
+ }
+
+ Result MemFreqMtcTable(u32 *ptr) {
+ static const DramId dramId = [] {
+ DramId id = GetDramId();
+ id = HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF;
+ return id;
+ }();
+
+ static const MtcTableIndex mtcIndex = [] {
+ MtcTableIndex idx = GetMtcDramIndex(dramId);
+ /* If for some reason this happens, there is no chance of recovering this. */
+ if (idx == MtcTableIndex_Invalid) {
+ AbortInvalidMtc("Invalid dramId");
+ }
+ return idx;
+ }();
+
+ /* Offset to dram id specific mtc table. */
+ static const u32 mtcOffset = GetMtcOffset(mtcIndex);
+
+ /* Offset from 1600MHz pointer to 204Mhz table start. */
+ constexpr u32 StartAdjustment = offsetof(MarikoMtcTable, rate_khz) + sizeof(MarikoMtcTable) * (mariko::MtcTableCountDefault - 1);
+ u8 *startPtr = reinterpret_cast(ptr) - StartAdjustment;
+ MarikoMtcTable *table = reinterpret_cast(startPtr + mtcOffset);
+ R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault));
+
+ PrepareMtcMemoryRegion(startPtr, table);
+ table = reinterpret_cast(startPtr);
+
+ if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) {
+ AbortInvalidMtc("Failed mtc validation");
+ }
+
+ if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
+ R_SKIP();
+ }
+
+ MtcExtendTables(table);
+
+ if (R_FAILED(MtcValidateAllTables(table, newEmcList.data(), newEmcList.size()))) {
+ AbortInvalidMtc("Failed mtc validation");
+ }
+
+ for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) {
+ MemMtcTableAutoAdjust(&table[i]);
+ MemMtcPllmbDivisor(&table[i]);
+ }
+
+ R_SUCCEED();
+ }
+
+ Result MemFreqDvbTable(u32 *ptr) {
+ DvbEntry *default_end = reinterpret_cast(ptr);
+ DvbEntry *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();
+ }
+
+ s32 max0 = 1050;
+ s32 max1 = 1025;
+ s32 max2 = 1000;
+
+ if (C.marikoSocVmax && C.marikoSocVmax > 1000) {
+ max0 = C.marikoSocVmax;
+ max1 = C.marikoSocVmax;
+ max2 = C.marikoSocVmax;
+ }
+
+ constexpr s32 MinVolt = 637;
+
+ auto ClampVolt = [&](s32 value, s32 max, s32 voltAdd) {
+ return std::clamp(value + voltAdd, MinVolt, max);
+ };
+
+ auto DvbVolt = [&](s32 zero, s32 one, s32 two, u32 index) {
+ const s32 overrideVoltage = C.marikoSocVoltArray[index];
+ s32 voltAdd = 25 * C.emcDvbShift;
+
+ if (overrideVoltage) {
+ zero = one = two = overrideVoltage;
+ voltAdd = 0;
+ }
+
+ return std::array{
+ ClampVolt(zero, max0, voltAdd),
+ ClampVolt(one, max1, voltAdd),
+ ClampVolt(two, max2, voltAdd)
+ };
+ };
+
+ #define DVB(v) \
+ static_cast((v)[0]), \
+ static_cast((v)[1]), \
+ static_cast((v)[2])
+
+ #define DVB_OC(one, zero, two, idx) \
+ DVB(DvbVolt(one, zero, two, idx))
+
+ DvbEntry emcDvbOcTableBrackets[] = {
+ { 204000, { 637, 637, 637, }, },
+ { 1331200, { 650, 637, 637, }, },
+ { 1600000, { 675, 650, 637, }, },
+ { 1866000, { DVB_OC( 700, 675, 650, 0) }, },
+ { 2000000, { DVB_OC( 712, 687, 662, 1) }, },
+ { 2133000, { DVB_OC( 725, 700, 675, 2) }, },
+ { 2200000, { DVB_OC( 737, 712, 687, 3) }, },
+ { 2266000, { DVB_OC( 750, 725, 700, 4) }, },
+ { 2333000, { DVB_OC( 762, 737, 712, 5) }, },
+ { 2400000, { DVB_OC( 775, 750, 725, 6) }, },
+ { 2433000, { DVB_OC( 787, 762, 737, 7) }, },
+ { 2466000, { DVB_OC( 800, 775, 750, 8) }, },
+ { 2533000, { DVB_OC( 812, 787, 762, 9) }, },
+ { 2566000, { DVB_OC( 825, 800, 775, 10) }, },
+ { 2600000, { DVB_OC( 837, 812, 787, 11) }, },
+ { 2666000, { DVB_OC( 850, 825, 800, 12) }, },
+ { 2700000, { DVB_OC( 875, 850, 825, 13) }, },
+ { 2733000, { DVB_OC( 887, 862, 837, 14) }, },
+ { 2766000, { DVB_OC( 912, 887, 862, 15) }, },
+ { 2800000, { DVB_OC( 925, 900, 875, 16) }, },
+ { 2833000, { DVB_OC( 937, 912, 887, 17) }, },
+ { 2900000, { DVB_OC( 950, 925, 900, 18) }, },
+ { 2933000, { DVB_OC( 962, 937, 912, 19) }, },
+ { 3000000, { DVB_OC( 975, 950, 925, 20) }, },
+ { 3033000, { DVB_OC( 987, 962, 937, 21) }, },
+ { 3100000, { DVB_OC(1000, 975, 950, 22) }, },
+ { 3133000, { DVB_OC(1025, 1000, 975, 23) }, },
+ { 3166000, { DVB_OC(1037, 1012, 987, 24) }, },
+ { 3200000, { DVB_OC(1050, 1025, 1000, 25) }, },
+ { 3266000, { DVB_OC(1075, 1050, 1025, 26) }, },
+ { 3333000, { DVB_OC(1100, 1075, 1050, 27) }, },
+ { ~0u, { }, },
+ };
+ #undef DVB
+ #undef DVB_OC
+
+ const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity);
+ DvbEntry emcDvbTableOc[DvbTableCapacity] = {};
+
+ u32 bracketIndex = 0;
+ for (size_t i = 0; i < dvbCount; ++i) {
+ const u32 freq = (i == dvbCount - 1) ? static_cast(newEmcList.back()) : newEmcList[i];
+ while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
+ ++bracketIndex;
+ }
+
+ emcDvbTableOc[i].freq = freq;
+ std::memcpy(emcDvbTableOc[i].volt, emcDvbOcTableBrackets[bracketIndex].volt, sizeof(emcDvbTableOc[i].volt));
+ }
+
+ /* Clear the entire 32-entry region */
+ std::memset(mem_dvb_table_head, 0, DvbTableCapacity * sizeof(DvbEntry));
+ std::memcpy(mem_dvb_table_head, emcDvbTableOc, dvbCount * sizeof(DvbEntry));
+
+ R_SUCCEED();
+ }
+
+ Result MemFreqMax(u32 *ptr) {
+ if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
+ R_SKIP();
+ }
+
+ PATCH_OFFSET(ptr, C.marikoEmcMaxClock);
+ R_SUCCEED();
+ }
+
+ Result MemMtcTableAsm(u32 *ptr) {
+ constexpr u32 AddpOffset = 1;
+ constexpr u32 BrOffset = 12;
+ constexpr u32 MovOffset = 10;
+
+ /* Ensure we don't dereference memory before nso start. */
+ R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
+
+ u32 adrp = *(ptr - AddpOffset);
+ R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
+
+ /* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
+ /* The correct instructions will always be x0 since the mtcTable pointer is returned. */
+
+ /* Pray this does not break. */
+ u32 br = *(ptr - BrOffset);
+ R_UNLESS(AsmCompareBrNoRd(br, MtcBrAsm), ldr::ResultInvalidMtcTablePattern());
+
+ /* Pray this does not break either. */
+ u32 mov = *(ptr - MovOffset);
+ R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
+
+ u8 movRd = asm_get_rd(mov);
+ u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), movRd);
+
+ PATCH_OFFSET(ptr - BrOffset, NopIns);
+ PATCH_OFFSET(ptr - MovOffset, movCountPatch);
+
+ R_SUCCEED();
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp
new file mode 100644
index 00000000..99531813
--- /dev/null
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/pcv_mariko_mtc.hpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Switch-OC-Suite
+ *
+ * Copyright (c) 2023 hanai3Bi
+ *
+ * Copyright (c) B3711
+ *
+ * Copyright (c) Souldbminer and Horizon OC Contributors
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "../pcv.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ void MtcGenerateFreqTables();
+ Result MemFreqMtcTable(u32 *ptr);
+ Result MemFreqDvbTable(u32 *ptr);
+ Result MemFreqMax(u32 *ptr);
+ Result MemMtcTableAsm(u32 *ptr);
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.cpp
similarity index 96%
rename from Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.cpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.cpp
index 61842b3d..ecb4d47f 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.cpp
@@ -1,47 +1,47 @@
-/*
- * Copyright (c) Lightos_
- *
- * 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.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include "../oc_common.hpp"
-#include "timing_tables.hpp"
-
-namespace ams::ldr::hoc::pcv::mariko {
-
- const ReplacePatch g_rext_table[] = {
- {2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
- {2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
- {2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
- {2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
- {2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
- {2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
- {2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
- {2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
- {2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
- {3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
- {3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
- };
-
- const u32 g_rext_table_size = sizeof(g_rext_table) / sizeof(g_rext_table[0]);
-
- const ReplacePatch *FindRext() {
- for (u32 i = 0; i < g_rext_table_size; i++) {
- if (g_rext_table[i].freq >= C.marikoEmcMaxClock) {
- return &g_rext_table[i];
- }
- }
- return nullptr;
- }
-
-}
+/*
+ * Copyright (c) Lightos_
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "../../oc_common.hpp"
+#include "timing_tables.hpp"
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ const ReplacePatch g_rext_table[] = {
+ {2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
+ {2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
+ {2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
+ {2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
+ {2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
+ {2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
+ {2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
+ {2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
+ {2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
+ {3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
+ {3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
+ };
+
+ const u32 g_rext_table_size = sizeof(g_rext_table) / sizeof(g_rext_table[0]);
+
+ const ReplacePatch *FindRext() {
+ for (u32 i = 0; i < g_rext_table_size; i++) {
+ if (g_rext_table[i].freq >= C.marikoEmcMaxClock) {
+ return &g_rext_table[i];
+ }
+ }
+ return nullptr;
+ }
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp
similarity index 96%
rename from Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.hpp
rename to Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp
index c23b812d..d682000e 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/mariko/timing_tables.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/mariko/timing_tables.hpp
@@ -1,31 +1,31 @@
-/*
- * Copyright (c) Lightos_
- *
- * 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.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#pragma once
-#include
-
-namespace ams::ldr::hoc::pcv::mariko {
-
- struct ReplacePatch {
- u32 freq;
- u32 rext;
- };
-
- extern const ReplacePatch g_rext_table[];
- extern const u32 g_rext_table_size;
- const ReplacePatch *FindRext();
-
-}
+/*
+ * Copyright (c) Lightos_
+ *
+ * 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.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+#include
+
+namespace ams::ldr::hoc::pcv::mariko {
+
+ struct ReplacePatch {
+ u32 freq;
+ u32 rext;
+ };
+
+ extern const ReplacePatch g_rext_table[];
+ extern const u32 g_rext_table_size;
+ const ReplacePatch *FindRext();
+
+}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp
index 74e540eb..c50f55a8 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp
@@ -22,8 +22,14 @@
#include "../oc_common.hpp"
#include "pcv_common.hpp"
-#include "pcv_erista.hpp"
-#include "pcv_mariko.hpp"
+
+#include "erista/pcv_erista_cpu.hpp"
+#include "erista/pcv_erista_gpu.hpp"
+#include "erista/pcv_erista_mtc.hpp"
+#include "erista/pcv_erista.hpp"
+
+#include "mariko/pcv_mariko.hpp"
+
#include "pcv_hook.hpp"
namespace ams::ldr::hoc::pcv {
@@ -188,6 +194,7 @@ namespace ams::ldr::hoc::pcv {
}
++entry;
}
+
if (C.commonGpuVoltOffset && !(isMariko ? C.marikoGpuUV : C.eristaGpuUV)) {
cvb_entry_t *entry = static_cast(gpu_cvb_table_head);
for (size_t i = 0; i < customize_entry_count; ++i) {
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp
deleted file mode 100644
index 6e46f8fa..00000000
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.hpp
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) Switch-OC-Suite
- *
- * Copyright (c) 2023 hanai3Bi
- *
- * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors
- *
- * 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.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#pragma once
-
-#include "../oc_common.hpp"
-#include "pcv_common.hpp"
-#include "pcv_asm.hpp"
-
-namespace ams::ldr::hoc::pcv::erista {
-
- constexpr cvb_entry_t CpuCvbTableDefault[] = {
- // CPU_PLL_CVB_TABLE_ODN
- { 204000, {721094}, { } },
- { 306000, {754040}, { } },
- { 408000, {786986}, { } },
- { 510000, {819932}, { } },
- { 612000, {852878}, { } },
- { 714000, {885824}, { } },
- { 816000, {918770}, { } },
- { 918000, {951716}, { } },
- { 1020000, {984662}, { -2875621, 358099, -8585} },
- { 1122000, {1017608}, { -52225, 104159, -2816} },
- { 1224000, {1050554}, { 1076868, 8356, -727} },
- { 1326000, {1083500}, { 2208191, -84659, 1240} },
- { 1428000, {1116446}, { 2519460, -105063, 1611} },
- { 1581000, {1130000}, { 2889664, -122173, 1834} },
- { 1683000, {1168000}, { 5100873, -279186, 4747} },
- { 1785000, {1227500}, { 5100873, -279186, 4747} },
- { },
- };
-
- constexpr u32 CpuVoltOfficial = 1227;
- constexpr u32 CpuVminOfficial = 825;
- constexpr u32 CpuTune0Low = 0xFFEAD0FF;
-
- constexpr u32 CpuVoltL4T = 1257'000;
-
- static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 };
- static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "Invalid cpuVoltDvfsPattern size");
-
- static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 };
- static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "Invalid cpuVoltageThermalPattern size");
-
- constexpr u32 GpuClkPllLimit = 2'600'000;
- constexpr u32 GpuClkPllMax = 921'600'000;
- constexpr u32 GpuVminOfficial = 810;
-
- constexpr u16 CpuMinVolts[] = { 950, 850, 825, 810 };
-
- static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, };
- static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern");
-
- static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 };
- static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "Invalid gpuVoltageThermalPattern size");
-
- /* GPU Max Clock asm Pattern:
- *
- * MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
- * sf | opc | | hw | imm16 | Rd
- * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
- * 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
- *
- * MOVK W11, #0xE, LSL#16 16 0xE 0xB (11)
- * sf | opc | | hw | imm16 | Rd
- * #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
- * 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
- */
- inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
-
- inline bool GpuMaxClockPatternFn(u32 *ptr32) {
- return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
- };
-
- constexpr cvb_entry_t GpuCvbTableDefault[] = {
- // NA_FREQ_CVB_TABLE
- { 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } },
- { 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } },
- { 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } },
- { 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } },
- { 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } },
- { 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } },
- { 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } },
- { 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } },
- { 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } },
- { 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } },
- { 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } },
- { 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } },
- { },
- };
-
- constexpr u32 EmcListDefault[] = { 40800, 68000, 102000, 204000, 408000, 665600, 800000, 1065600, 1331200, 1600000, };
- constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
- constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
-
- constexpr u32 MemVoltHOS = 1125'000;
- constexpr u32 EmcClkPllmLimit = 1866'000'000;
-
- constexpr u32 MTC_TABLE_REV = 7;
- constexpr u32 MtcTableCountDefault = 10;
-
- constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault;
- constexpr u32 MtcFullTableCount = 3;
-
- /* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
- enum DramId {
- ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0,
- ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1,
- ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2,
- ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4,
- ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7,
- };
-
- enum MtcTableIndex {
- T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH, WT:C */
- T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */
- T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */
- MtcTableIndex_Invalid = 3,
- };
-
- struct MtcDramIndex {
- DramId dramId;
- MtcTableIndex index;
- };
-
- /* TODO: Test 6gb and 8gb. */
- const inline MtcDramIndex mtcIndexTable[] = {
- { ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, },
- { ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC, T210SdevEmcDvfsTableS4gb01, },
- { ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, },
- { ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX, T210SdevEmcDvfsTableS6gb01, },
- { ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, },
- };
-
- constexpr u32 MtcBrAsm = 0xD61F0140;
- constexpr u32 MtcMovAsm = 0x52800148;
- constexpr u32 MtcAdrpAsm = 0xD0000081;
- constexpr u32 MtcBlIns = 0x97ffae64;
- constexpr u32 MtcAddAsm = 0x91131821;
-
- ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
- /* This builds an address that gets returned, so the register must be x0 by convention. */
- return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
- }
-
- void Patch(uintptr_t mapped_nso, size_t nso_size);
-
-}
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp
index 06acacda..fcc6a47a 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_hook.hpp
@@ -37,6 +37,9 @@
".text\n"); \
extern "C" __attribute__((visibility("hidden"))) type *name
+#define DECLARE_HOOK_PAYLOAD_PTR(type, name) \
+ extern "C" __attribute__((visibility("hidden"))) type *name
+
#define HOOK_PAYLOAD_PTR(type, name) \
([]() -> type * { \
type **_hoc_pp; \
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp
deleted file mode 100644
index 111e8a55..00000000
--- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp
+++ /dev/null
@@ -1,1545 +0,0 @@
-/*
- * Copyright (C) Switch-OC-Suite
- *
- * Copyright (c) 2023 hanai3Bi
- *
- * Copyright (c) B3711
- *
- * Copyright (c) Souldbminer and Horizon OC Contributors
- *
- * 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.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include "pcv.hpp"
-#include "../mtc_timing_value.hpp"
-#include "../mariko/calculate_timings.hpp"
-
-namespace ams::ldr::hoc::pcv::mariko {
-
- Result GpuVoltDVFS(u32 *ptr) {
- /* Check for valid pattern. */
- for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) {
- if (*(ptr + i + 1) != gpuDVFSPattern[i]) {
- R_THROW(ldr::ResultInvalidGpuDvfs());
- }
- }
-
- /* Default value is 1050mV. */
- if (C.marikoGpuVmax) {
- PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
- }
-
- if (C.marikoGpuVmin) {
- PATCH_OFFSET(ptr, C.marikoGpuVmin);
- }
-
- R_SUCCEED();
- }
-
- Result GpuVoltThermals(u32 *ptr) {
- if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
- R_THROW(ldr::ResultInvalidGpuDvfs());
- }
-
- // if (C.marikoGpuBootVolt) {
- // PATCH_OFFSET(ptr - 3, C.marikoGpuBootVolt);
- // }
-
- if (C.marikoGpuVmin) {
- PATCH_OFFSET(ptr, C.marikoGpuVmin);
- PATCH_OFFSET(ptr + 3, C.marikoGpuVmin);
- PATCH_OFFSET(ptr + 6, C.marikoGpuVmin);
- PATCH_OFFSET(ptr + 9, C.marikoGpuVmin);
- PATCH_OFFSET(ptr + 12, C.marikoGpuVmin);
- }
-
- R_SUCCEED();
- }
-
- u32 CapCpuClock() {
- u32 cpuCap = allowedCpuMaxFrequencies[0];
-
- for (u32 freq : allowedCpuMaxFrequencies) {
- if (C.marikoCpuMaxClock >= freq) {
- cpuCap = freq;
- } else {
- break;
- }
- }
- return cpuCap;
- }
-
- 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.marikoCpuUVHigh) {
- PATCH_OFFSET(ptr, CapCpuClock());
- } else {
- PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
- }
-
- R_SUCCEED();
- }
-
- Result CpuVoltDVFS(u32 *ptr) {
- CvbMeta *cpuCvbMeta = reinterpret_cast(reinterpret_cast(ptr) - offsetof(CvbMeta, vmin));
-
- R_UNLESS(cpuCvbMeta->highVmin == CpuHighVminOfficial, ldr::ResultInvalidCpuMinVolt());
- R_UNLESS(cpuCvbMeta->unkStepMaybe == 38, ldr::ResultInvalidCpuMinVolt());
- R_UNLESS(cpuCvbMeta->vmax == CpuVoltOfficial, ldr::ResultInvalidCpuMinVolt());
- R_UNLESS(cpuCvbMeta->unkScale2 == 1000, ldr::ResultInvalidCpuMinVolt());
- R_UNLESS(cpuCvbMeta->speedoScale == 100, ldr::ResultInvalidCpuMinVolt());
- R_UNLESS(cpuCvbMeta->voltageScale == 1000, ldr::ResultInvalidCpuMinVolt());
- R_UNLESS(cpuCvbMeta->unkZero5 == 0, ldr::ResultInvalidCpuMinVolt());
-
- if (C.marikoCpuLowVmin) {
- PATCH_OFFSET(&(cpuCvbMeta->vmin), C.marikoCpuLowVmin);
- }
-
- if (C.marikoCpuHighVmin) {
- PATCH_OFFSET(&(cpuCvbMeta->highVmin), C.marikoCpuHighVmin);
- }
-
- if (C.marikoCpuMaxVolt) {
- PATCH_OFFSET(&(cpuCvbMeta->vmax), C.marikoCpuMaxVolt);
- }
-
- R_SUCCEED();
- }
-
- Result CpuVoltThermals(u32 *ptr) {
- if (std::memcmp(ptr, cpuVoltThermalData, sizeof(cpuVoltThermalData))) {
- R_THROW(ldr::ResultInvalidCpuMinVolt());
- }
-
- if (C.marikoCpuLowVmin) {
- PATCH_OFFSET(ptr, C.marikoCpuLowVmin);
- PATCH_OFFSET(ptr + 3, C.marikoCpuLowVmin);
- }
-
- if (C.marikoCpuMaxVolt) {
- PATCH_OFFSET(ptr - 2, C.marikoCpuMaxVolt);
- PATCH_OFFSET(ptr - 5, C.marikoCpuMaxVolt);
- PATCH_OFFSET(ptr + 1, C.marikoCpuMaxVolt);
- PATCH_OFFSET(ptr + 4, C.marikoCpuMaxVolt);
- }
-
- R_SUCCEED();
- }
-
- Result CpuVoltDfll(u32 *ptr) {
- CvbCpuDfllData *entry = reinterpret_cast(ptr);
-
- R_UNLESS(entry->tune0_low == 0xFFCF, ldr::ResultInvalidCpuVoltDfllEntry());
- R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
- R_UNLESS(entry->tune1_low == 0x12207FF, ldr::ResultInvalidCpuVoltDfllEntry());
- R_UNLESS(entry->tune1_high == 0x3FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry());
-
- switch (C.marikoCpuUVLow) {
- case 1:
- PATCH_OFFSET(&(entry->tune0_low), 0xffa0);
- PATCH_OFFSET(&(entry->tune0_high), 0xffff);
- PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x0);
- break;
- case 2:
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
- break;
- case 3:
- PATCH_OFFSET(&(entry->tune0_low), 0xffdf);
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
- break;
- case 4:
- PATCH_OFFSET(&(entry->tune0_low), 0xffff);
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
- break;
- case 5:
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
- break;
- case 6:
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
- break;
- case 7:
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
- break;
- case 8:
- PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
- break;
- case 9:
- PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
- break;
- case 10:
- PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
- break;
- case 11:
- PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
- break;
- case 12:
- PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
- break;
- default:
- break;
- }
-
- switch (C.marikoCpuUVHigh) {
- case 1:
- PATCH_OFFSET(&(entry->tune1_high), 0x0);
- PATCH_OFFSET(&(entry->tune0_high), 0xffff);
- break;
- case 2:
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
- break;
- case 3:
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
- break;
- case 4:
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
- break;
- case 5:
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
- break;
- case 6:
- PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
- PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
- break;
- case 7:
- case 8:
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
- break;
- case 9:
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
- break;
- case 10:
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
- break;
- case 11:
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
- break;
- case 12:
- PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
- PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
- break;
- default:
- break;
- }
-
- 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, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[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;
- case 3:
- max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT15)->freq;
- break;
- case 4:
- max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHighUV)->freq;
- break;
- default:
- max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
- break;
- }
-
- u32 asm_patch[2] = {
- asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
- asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
- };
-
- PATCH_OFFSET(ptr32, asm_patch[0]);
- PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
-
- R_SUCCEED();
- }
-
- Result GpuFreqPllMax(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();
- }
-
- Result GpuFreqPllLimit(u32 *ptr) {
- u32 prev_freq = *(ptr - 1);
-
- if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
- R_THROW(ldr::ResultInvalidGpuPllEntry());
- }
-
- PATCH_OFFSET(ptr, 3600000);
-
- R_SUCCEED();
- }
-
- 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.1: MC Registers
- * Section 18.11.2: EMC Registers
- */
-
- #define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
- TABLE->burst_regs.PARAM = VALUE; \
- TABLE->shadow_regs_ca_train.PARAM = VALUE; \
- TABLE->shadow_regs_rdwr_train.PARAM = VALUE;
-
- const double tCK_avg = 1000'000.0 / table->rate_khz;
-
- #define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
-
- /* Ram power down */
- /* B31: DRAM_CLKSTOP_PD */
- /* B30: DRAM_CLKSTOP_SR */
- /* B29: DRAM_ACPD */
- if (C.hpMode) {
- WRITE_PARAM_ALL_REG(table, emc_cfg, 0x13200000);
- } else {
- WRITE_PARAM_ALL_REG(table, emc_cfg, 0xF3200000);
- }
-
- u32 trefbw = refresh_raw + 0x40;
- trefbw = MIN(trefbw, static_cast(0x3FFF));
-
- const u32 dyn_self_ref_control = (static_cast(7605.0 / tCK_avg) + 260) | (table->burst_regs.emc_dyn_self_ref_control & 0xffff0000);
-
- CalculateTimings(tCK_avg, table->rate_khz);
-
- 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_rc, MIN(GET_CYCLE_CEIL(tRC), static_cast(0xB9)));
- WRITE_PARAM_ALL_REG(table, emc_ras, MIN(GET_CYCLE_CEIL(tRAS), static_cast(0x7F)));
- WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD));
- WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb));
- WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab));
- WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb));
- WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(tXSR), static_cast(0x3fe)));
- WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(tXSR), static_cast(0x3fe)));
- WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW));
- WRITE_PARAM_ALL_REG(table, emc_trpab, MIN(GET_CYCLE_CEIL(tRPab), static_cast(0x3F)));
- WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR));
- WRITE_PARAM_ALL_REG(table, emc_tcke, GET_CYCLE_CEIL(7.425) + 2);
- WRITE_PARAM_ALL_REG(table, emc_tpd, GET_CYCLE_CEIL(tXP));
- WRITE_PARAM_ALL_REG(table, emc_tclkstop, tCLKSTOP);
- WRITE_PARAM_ALL_REG(table, emc_r2p, tR2P);
- WRITE_PARAM_ALL_REG(table, emc_r2w, tR2W);
- WRITE_PARAM_ALL_REG(table, emc_trtm, tRTM);
- WRITE_PARAM_ALL_REG(table, emc_tratm, tRATM);
- WRITE_PARAM_ALL_REG(table, emc_w2p, tW2P);
- WRITE_PARAM_ALL_REG(table, emc_w2r, tW2R);
- WRITE_PARAM_ALL_REG(table, emc_twtm, tWTM);
- WRITE_PARAM_ALL_REG(table, emc_twatm, tWATM);
- WRITE_PARAM_ALL_REG(table, emc_rext, rext);
- WRITE_PARAM_ALL_REG(table, emc_wext, (table->rate_khz >= 2533000) ? 0x19 : 0x16);
- WRITE_PARAM_ALL_REG(table, emc_refresh, refresh_raw);
- WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, refresh_raw / 4);
- WRITE_PARAM_ALL_REG(table, emc_trefbw, trefbw);
- WRITE_PARAM_ALL_REG(table, emc_dyn_self_ref_control, dyn_self_ref_control);
- WRITE_PARAM_ALL_REG(table, emc_pdex2wr, pdex2rw);
- WRITE_PARAM_ALL_REG(table, emc_pdex2rd, pdex2rw);
- WRITE_PARAM_ALL_REG(table, emc_pchg2pden, GET_CYCLE_CEIL(1.763));
- WRITE_PARAM_ALL_REG(table, emc_ar2pden, GET_CYCLE_CEIL(1.75));
- WRITE_PARAM_ALL_REG(table, emc_pdex2cke, GET_CYCLE_CEIL(1.05));
- WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(14.0));
- WRITE_PARAM_ALL_REG(table, emc_cke2pden, GET_CYCLE_CEIL(8.499));
- WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(pdex2mrr));
- WRITE_PARAM_ALL_REG(table, emc_rw2pden, tWTPDEN);
- WRITE_PARAM_ALL_REG(table, emc_einput, einput);
- WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
- WRITE_PARAM_ALL_REG(table, emc_obdly, obdly);
- WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly);
- WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv);
- WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
- WRITE_PARAM_ALL_REG(table, emc_quse, quse);
- WRITE_PARAM_ALL_REG(table, emc_wdv, wdv);
- WRITE_PARAM_ALL_REG(table, emc_wsv, wsv);
- WRITE_PARAM_ALL_REG(table, emc_wev, wev);
- WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
- WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
- WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
- WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
- WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
- WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
- WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
- WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
- WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
- WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
- WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
- WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
- WRITE_PARAM_ALL_REG(table, emc_cmd_brlshft_2, 0x24);
- WRITE_PARAM_ALL_REG(table, emc_cmd_brlshft_3, 0x24);
-
- /* This needs some clean up. */
- constexpr double MC_ARB_DIV = 4.0;
- constexpr u32 MC_ARB_SFA = 2;
-
- table->burst_mc_regs.mc_emem_arb_cfg = table->rate_khz / (33.3 * 1000) / MC_ARB_DIV;
- table->burst_mc_regs.mc_emem_arb_timing_rcd = CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2;
- table->burst_mc_regs.mc_emem_arb_timing_rp = CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1;
- table->burst_mc_regs.mc_emem_arb_timing_rc = CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1;
- table->burst_mc_regs.mc_emem_arb_timing_ras = CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2;
- table->burst_mc_regs.mc_emem_arb_timing_faw = CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1;
- table->burst_mc_regs.mc_emem_arb_timing_rrd = CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1;
- table->burst_mc_regs.mc_emem_arb_timing_rfcpb = CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV) - 1;
- table->burst_mc_regs.mc_emem_arb_timing_rap2pre = CEIL(tR2P / MC_ARB_DIV);
- table->burst_mc_regs.mc_emem_arb_timing_wap2pre = CEIL(tW2P / MC_ARB_DIV) + MC_ARB_SFA;
-
- /* Two consecutive reads between two different dram modules. */
- /* Only above 1 for 8gb ram. */
- if (table->burst_mc_regs.mc_emem_arb_timing_r2r > 1) {
- table->burst_mc_regs.mc_emem_arb_timing_r2r = CEIL(table->burst_regs.emc_rext / 4) - 1 + MC_ARB_SFA;
- }
-
- /* Same as r2r but for write. */
- if (table->burst_mc_regs.mc_emem_arb_timing_w2w > 1) {
- table->burst_mc_regs.mc_emem_arb_timing_w2w = CEIL(table->burst_regs.emc_wext / MC_ARB_DIV) - 1 + MC_ARB_SFA;
- }
-
- table->burst_mc_regs.mc_emem_arb_timing_r2w = CEIL(tR2W / MC_ARB_DIV) - 1 + MC_ARB_SFA;
- table->burst_mc_regs.mc_emem_arb_timing_w2r = CEIL(tW2R / MC_ARB_DIV) - 1 + MC_ARB_SFA;
-
- u32 da_turns = 0;
- da_turns |= u8(table->burst_mc_regs.mc_emem_arb_timing_r2w / 2) << 16;
- da_turns |= u8(table->burst_mc_regs.mc_emem_arb_timing_w2r / 2) << 24;
- table->burst_mc_regs.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 |= (table->burst_mc_regs.mc_emem_arb_timing_rc / 2);
- da_covers |= (r_cover << 8);
- da_covers |= (w_cover << 16);
- table->burst_mc_regs.mc_emem_arb_da_covers = da_covers;
-
- constexpr u32 AtomsPerDvfsPulse = 0x7;
- constexpr u32 McEmcSameFreq = 0x0;
- const u32 expiringSoonSlackThreshold = [&] {
- switch (table->rate_khz) {
- case 2966000:
- case 3100000:
- case 3133000:
- case 3200000:
- return 0x12u;
- default:
- return 0x13u;
- }
- }();
-
- const u32 priorityInversionIsoThreshold = GET_CYCLE_CEIL(7.5);
- constexpr u32 EmcReqB2bXfer = 0x0;
- const u32 priorityInversionThreshold = GET_CYCLE_CEIL(22.5);
- const u32 bc2aaHoldoffThreshold = table->burst_mc_regs.mc_emem_arb_timing_rc + 1;
-
- const u32 mc_emem_arb_misc0 = (AtomsPerDvfsPulse << 28) | (McEmcSameFreq << 27) | (expiringSoonSlackThreshold << 21) | (priorityInversionIsoThreshold << 16) | (EmcReqB2bXfer << 15) | (priorityInversionThreshold << 8) | (bc2aaHoldoffThreshold << 0);
- table->burst_mc_regs.mc_emem_arb_misc0 = mc_emem_arb_misc0;
-
- table->la_scale_regs.mc_mll_mpcorer_ptsa_rate = 0x115;
-
- if (table->rate_khz >= 2133000) {
- table->la_scale_regs.mc_ftop_ptsa_rate = 0x1F;
- } else {
- table->la_scale_regs.mc_ftop_ptsa_rate = 0x1B;
- }
-
- table->la_scale_regs.mc_ptsa_grant_decrement = 0x17ff;
-
- constexpr u32 MaskHigh = 0xFF00FFFF;
- constexpr u32 Mask2 = 0xFFFFFF00;
- constexpr u32 Mask3 = 0xFF00FF00;
-
- const u32 allowance1 = static_cast(0x32000 / (table->rate_khz / 1000)) & 0xFF;
- const u32 allowance2 = static_cast(0x9C40 / (table->rate_khz / 1000)) & 0xFF;
- const u32 allowance3 = static_cast(0xB540 / (table->rate_khz / 1000)) & 0xFF;
- const u32 allowance4 = static_cast(0x9600 / (table->rate_khz / 1000)) & 0xFF;
- const u32 allowance5 = static_cast(0x8980 / (table->rate_khz / 1000)) & 0xFF;
-
- table->la_scale_regs.mc_latency_allowance_xusb_0 = (table->la_scale_regs.mc_latency_allowance_xusb_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_xusb_1 = (table->la_scale_regs.mc_latency_allowance_xusb_1 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_tsec_0 = (table->la_scale_regs.mc_latency_allowance_tsec_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_sdmmcaa_0 = (table->la_scale_regs.mc_latency_allowance_sdmmcaa_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_sdmmcab_0 = (table->la_scale_regs.mc_latency_allowance_sdmmcab_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_sdmmc_0 = (table->la_scale_regs.mc_latency_allowance_sdmmc_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_sdmmca_0 = (table->la_scale_regs.mc_latency_allowance_sdmmca_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_ppcs_1 = (table->la_scale_regs.mc_latency_allowance_ppcs_1 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_nvdec_0 = (table->la_scale_regs.mc_latency_allowance_nvdec_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_mpcore_0 = (table->la_scale_regs.mc_latency_allowance_mpcore_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_avpc_0 = (table->la_scale_regs.mc_latency_allowance_avpc_0 & MaskHigh) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_isp2_1 = allowance1 | (table->la_scale_regs.mc_latency_allowance_isp2_1 & Mask3) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_gpu_0 = allowance2 | (table->la_scale_regs.mc_latency_allowance_gpu_0 & Mask3) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_gpu2_0 = allowance2 | (table->la_scale_regs.mc_latency_allowance_gpu2_0 & Mask3) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_vic_0 = allowance3 | (table->la_scale_regs.mc_latency_allowance_vic_0 & Mask3) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_nvenc_0 = allowance4 | (table->la_scale_regs.mc_latency_allowance_nvenc_0 & Mask3) | (allowance1 << 16);
- table->la_scale_regs.mc_latency_allowance_hc_0 = (table->la_scale_regs.mc_latency_allowance_hc_0 & Mask2) | allowance5;
- table->la_scale_regs.mc_latency_allowance_hc_1 = (table->la_scale_regs.mc_latency_allowance_hc_1 & Mask2) | allowance1;
- table->la_scale_regs.mc_latency_allowance_vi2_0 = (table->la_scale_regs.mc_latency_allowance_vi2_0 & Mask2) | allowance1;
-
- table->dram_timings.t_rp = tRP_values[0];
- const u32 tRFCabStock = tRFC_values[0] * 2;
- table->dram_timings.t_rfc = tRFCabStock;
-
- table->dram_timings.rl = RL;
- table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast(mrw2);
- table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */
- table->emc_cfg_2 = 0x11083D;
- }
-
- void MemMtcPllmbDivisor(MarikoMtcTable *table) {
- constexpr u32 PllOscInKHz = 38400;
- constexpr u32 PllOscHalfKHz = 19200;
-
- double target_freq_d = static_cast(table->rate_khz);
-
- s32 divm_candidate_half = static_cast(table->rate_khz / PllOscHalfKHz);
-
- bool remainder_check = (table->rate_khz - PllOscInKHz * (table->rate_khz / PllOscInKHz)) > (table->rate_khz - PllOscHalfKHz * divm_candidate_half) && static_cast(((target_freq_d / PllOscHalfKHz - divm_candidate_half - 0.5) * 8192.0)) != 0;
-
- u32 divm_final = remainder_check + 1;
- table->pllmb_divm = divm_final;
-
- double div_step_d = static_cast(PllOscInKHz) / divm_final;
- s32 divn_integer = static_cast(table->rate_khz / div_step_d);
- table->pllmb_divn = divn_integer;
-
- u32 divn_fraction = static_cast((target_freq_d / div_step_d - divn_integer - 0.5) * 8192.0);
-
- u32 actual_freq_khz = static_cast((divn_integer + 0.5 + divn_fraction * 0.000122070312) * div_step_d);
-
- if (table->rate_khz - 2366001 < 133999) {
- s32 divn_fraction_ssc = static_cast((actual_freq_khz * 0.997 / div_step_d - divn_integer - 0.5) * 8192.0);
-
- double delta_scaled = (0.3 / div_step_d + 0.3 / div_step_d) * (divn_fraction - divn_fraction_ssc);
- s32 delta_int = static_cast(delta_scaled);
- double delta_frac = delta_scaled - delta_int;
-
- u32 setup_value = 0;
- if (delta_frac <= 0.5) {
- double round_val = (delta_int + ROUND(delta_frac + delta_frac)) ? 0.5 : 0.0;
- setup_value = ROUND(delta_frac + delta_frac) ? static_cast(round_val + round_val) | 0x1000 : static_cast(round_val);
- } else {
- s32 frac_doubled = ROUND(delta_frac - 0.5 + delta_frac - 0.5);
- double round_val = 1.0;
- setup_value = frac_doubled ? static_cast(round_val) : static_cast(round_val + round_val) | 0x1000;
- }
-
- u32 ctrl1 = static_cast(divn_fraction_ssc) | (static_cast(divn_fraction) << 16);
- u32 ctrl2 = static_cast(divn_fraction) | (static_cast(setup_value) << 16);
-
- table->pllm_ss_ctrl1 = ctrl1;
- table->pllm_ss_ctrl2 = ctrl2;
- table->pllmb_ss_ctrl1 = ctrl1;
- table->pllmb_ss_ctrl2 = ctrl2;
- } else {
- table->pllm_ss_cfg &= 0xBFFFFFFF;
- table->pllmb_ss_cfg &= 0xBFFFFFFF;
-
- u64 pair = (static_cast(divn_fraction) << 32) | static_cast(table->rate_khz);
- u32 pll_misc = (table->pllm_ss_ctrl2 & 0xFFFF0000) | static_cast((pair - actual_freq_khz) >> 32);
-
- table->pllm_ss_ctrl2 = pll_misc;
- table->pllmb_ss_ctrl2 = pll_misc;
- }
- }
-
- namespace {
- std::vector newEmcList;
- u32 *nsoStart;
- size_t g_nso_size = 0;
- uintptr_t g_cave_cursor = 0;
- }
-
- static uintptr_t CaveReserve(size_t count) {
- if (g_pcv_cave == 0 || g_cave_cursor == 0) {
- return 0;
- }
- if (g_cave_cursor + count * sizeof(u32) > g_pcv_cave + g_pcv_cave_size) {
- return 0;
- }
- const uintptr_t entry = g_cave_cursor;
- g_cave_cursor += count * sizeof(u32);
- return entry;
- }
-
- #if HOC_UART_LOG
- /* Redirect pcv's NvLog() calls to UART */
- Result NvLogUartRedirect(u32 *ptr) {
- const uintptr_t mapped_nso = reinterpret_cast(nsoStart);
- const size_t nso_size = g_nso_size;
- const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */
- const uintptr_t vsnprintf_addr = reinterpret_cast(ptr);
-
- /* NvLog via the VDD_SOC log */
- static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n";
- constexpr size_t FmtLen = sizeof(Fmt) - 1;
- uintptr_t strAddr = 0;
- {
- const char *hay = reinterpret_cast(mapped_nso);
- for (size_t i = 0; i + FmtLen <= nso_size; ++i) {
- if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; }
- }
- }
- if (strAddr == 0) {
- LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso);
- R_THROW(ldr::ResultInvalidNvLogRedirect());
- }
-
- uintptr_t nvlog_addr = 0;
- for (u32 *p = nsoStart; reinterpret_cast(p + 2) <= textEnd; ++p) {
- const uintptr_t pc = reinterpret_cast(p);
- if (!AsmIsAdrp(p[0])) {
- continue;
- }
- const uintptr_t adrpPage = (pc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(p[0]));
- if (adrpPage != (strAddr & ~static_cast(0xFFFu))) {
- continue;
- }
- const u32 reg = asm_get_rd(p[0]);
- if (!(AsmIsAddImm64(p[1]) && asm_get_rd(p[1]) == reg && AsmGetRn(p[1]) == reg && AsmGetImm12(p[1]) == (strAddr & 0xFFFu))) {
- continue;
- }
- for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) {
- if (AsmIsBl(p[k])) { nvlog_addr = AsmBranchTarget(p[k], pc + k * 4); break; }
- }
- if (nvlog_addr != 0) {
- break;
- }
- }
- if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) {
- LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso);
- R_THROW(ldr::ResultInvalidNvLogRedirect());
- }
-
- const uintptr_t helper = CaveReserve(40);
- if (helper == 0) {
- LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)",
- static_cast(g_pcv_cave), static_cast(g_pcv_cave_size));
- R_THROW(ldr::ResultInvalidNvLogRedirect());
- }
- u32 *t = reinterpret_cast(helper);
- size_t n = 0;
- auto emit = [&](u32 ins) { t[n] = ins; ++n; };
-
- emit(AsmMakeSubImm64(31, 31, 0x200));
- emit(AsmMakeStpImm64(0, 1, 31, 0x100));
- emit(AsmMakeStpImm64(2, 3, 31, 0x110));
- emit(AsmMakeStpImm64(4, 5, 31, 0x120));
- emit(AsmMakeStpImm64(6, 7, 31, 0x130));
- emit(AsmMakeStpqImm(0, 1, 31, 0x140));
- emit(AsmMakeStpqImm(2, 3, 31, 0x160));
- emit(AsmMakeStpqImm(4, 5, 31, 0x180));
- emit(AsmMakeStpqImm(6, 7, 31, 0x1A0));
- emit(AsmMakeStrImm64(30, 31, 0x1E0));
- emit(AsmMakeAddImm64(9, 31, 0x200)); emit(AsmMakeStrImm64(9, 31, 0x1C0)); /* __stack */
- emit(AsmMakeAddImm64(9, 31, 0x140)); emit(AsmMakeStrImm64(9, 31, 0x1C8)); /* __gr_top */
- emit(AsmMakeAddImm64(9, 31, 0x1C0)); emit(AsmMakeStrImm64(9, 31, 0x1D0)); /* __vr_top */
- emit(AsmMakeMovnW(9, 0x37)); emit(AsmMakeStrImm32(9, 31, 0x1D8)); /* __gr_offs = -56 */
- emit(AsmMakeMovnW(9, 0x7F)); emit(AsmMakeStrImm32(9, 31, 0x1DC)); /* __vr_offs = -128 */
- emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (buf) */
- emit(AsmMakeMovzW(1, 0x100)); /* size = 0x100 */
- emit(AsmMakeLdrImm64(2, 31, 0x100)); /* fmt (saved x0) */
- emit(AsmMakeAddImm64(3, 31, 0x1C0)); /* ap */
- emit(AsmMakeBl(helper + n * 4, vsnprintf_addr));
- emit(AsmMakeMovReg(1, 0)); /* len = retval */
- emit(AsmMakeCmpImm32(1, 0x100));
- { const size_t at = n; emit(AsmMakeBCond(helper + at * 4, helper + (at + 2) * 4, 0x3u)); } /* b.lo +2 */
- emit(AsmMakeMovzW(1, 0xFF)); /* clamp len */
- emit(AsmMakeAddImm64(0, 31, 0x00)); /* mov x0,sp (str) */
- emit(AsmMakeSvc(0x27)); /* svcOutputDebugString */
- emit(AsmMakeLdrImm64(30, 31, 0x1E0));
- emit(AsmMakeAddImm64(31, 31, 0x200));
- emit(RetIns);
-
- /* Redirect the call sites as patching the actual function causes crash */
- const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */
- size_t patchedSites = 0;
- if (HOC_PCV_NVLOG_PATCH) {
- for (u32 *p = nsoStart; reinterpret_cast(p + 1) <= textEnd; ++p) {
- if (!AsmIsBl(*p)) {
- continue;
- }
- const uintptr_t pc = reinterpret_cast(p);
- if (AsmBranchTarget(*p, pc) != nvlog_addr) {
- continue;
- }
- bool isFmtCall = false;
- for (u32 j = 1; j <= 8 && reinterpret_cast(p - j) >= reinterpret_cast(nsoStart); ++j) {
- const u32 w = *(p - j);
- if (AsmIsAdrp(w) && asm_get_rd(w) == 0) { /* adrp x0, */
- const uintptr_t wpc = pc - j * 4;
- const uintptr_t tgtPage = (wpc & ~static_cast(0xFFFu)) + static_cast(AsmAdrpPageOffset(w));
- if (tgtPage >= (roStart & ~static_cast(0xFFFu))) { isFmtCall = true; break; }
- }
- }
- if (isFmtCall) {
- PATCH_OFFSET(p, AsmMakeBl(pc, helper));
- ++patchedSites;
- }
- }
- }
-
- LOGGING("NvLogRedirect: stub@+%lx vsnprintf@+%lx helper@+%lx instr=%zu sites=%zu",
- nvlog_addr - mapped_nso, vsnprintf_addr - mapped_nso, helper - mapped_nso, n, patchedSites);
- R_SUCCEED();
- }
- #endif
-
- /* Relocate C2/C3Bus to avoid issues*/
- Result BusFreqReloc(u32 *ptr) {
- const u32 busReg = AsmGetRn(ptr[0]); /* ldr Xbuf,[Xbus,#0x10] : bus struct pointer */
- const u32 bufReg = asm_get_rd(ptr[0]); /* : freq-buffer arg */
- const u32 bufOff = AsmGetLdStImm64Off(ptr[0]); /* : bus->freqBuf offset */
- const u32 cntReg = asm_get_rd(ptr[1]); /* add Xcnt,Xbus,#0x18 : arg2 (&count) */
- const u32 railReg = asm_get_rd(ptr[2]); /* str Xrail,[Xbus,#0x50]: arg0 (rail) */
- u32 *call = ptr + 3; /* the bl to relocate */
- const uintptr_t realFn = AsmBranchTarget(*call, reinterpret_cast(call));
-
- /* Pick 3 scratch registers */
- u32 s[3], sc = 0;
- for (u32 r = 9; r <= 15 && sc < 3; ++r) {
- if (r != busReg && r != bufReg && r != cntReg && r != railReg) {
- s[sc++] = r;
- }
- }
- R_UNLESS(sc == 3, ldr::ResultInvalidBusFreqReloc());
-
- const uintptr_t tramp = CaveReserve(9);
- R_UNLESS(tramp != 0, ldr::ResultInvalidBusFreqReloc());
-
- const uintptr_t region = g_pcv_scratch + HocBusFreqBufOffset; /* [0]=counter, +0x10 + i*0x400 = bufs */
- u32 *t = reinterpret_cast(tramp);
- size_t n = 0;
- auto emit = [&](u32 ins) { t[n] = ins; ++n; };
- emit(AsmMakeAdrp(tramp + n * 4, region, s[0])); /* adrp s0, */
- emit(AsmMakeAddImm64(s[0], s[0], region & 0xFFFu)); /* add s0,s0,#lo */
- emit(AsmMakeLdrImm32(s[1], s[0], 0x00)); /* s1 = counter */
- emit(AsmMakeAddImm64(s[2], s[1], 1)); /* s2 = counter+1 */
- emit(AsmMakeStrImm32(s[2], s[0], 0x00)); /* counter++ */
- emit(AsmMakeAddImm64(s[0], s[0], 0x10)); /* s0 = region+0x10 (buffers) */
- emit(AsmMakeAddShiftedReg64(bufReg, s[0], s[1], 10)); /* Xbuf = s0 + counter*0x400 */
- emit(AsmMakeStrImm64(bufReg, busReg, bufOff)); /* bus[freqBuf] = Xbuf */
- emit(AsmMakeB(tramp + n * 4, realFn)); /* tail-call the real function */
-
- PATCH_OFFSET(call, AsmMakeBl(reinterpret_cast(call), tramp));
- const uintptr_t base = reinterpret_cast(nsoStart);
- (void) base;
- LOGGING("BusFreqReloc: call@+%lx -> tramp@+%lx realfn@+%lx (bus=x%u buf=x%u off=0x%x scratch=x%u,x%u,x%u)",
- reinterpret_cast(call) - base, tramp - base, realFn - base, busReg, bufReg, bufOff, s[0], s[1], s[2]);
- R_SUCCEED();
- }
-
- #if HOC_UART_LOG
- /* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */
- Result ForceVerbosity(u32 *ptr) {
- PATCH_OFFSET(&ptr[0], AsmMakeMovzW(0, static_cast(HOC_PCV_FORCE_VERBOSITY))); /* movz w0,#level */
- PATCH_OFFSET(&ptr[1], RetIns); /* ret */
- R_SUCCEED();
- }
- #endif
-
- /* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */
- Result EmcSocLutReloc(u32 *ptr) {
- constexpr u32 Window = 48;
-
- u32 *freqStore = ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, asm_compare_no_rd); /* str x?,[x8,#0x18] */
- u32 *voltStore = ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, asm_compare_no_rd); /* str w?,[x8,#0x48] */
- u32 *readLoad = ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, asm_compare_no_rd); /* ldr w?,[x9,#0x48] */
- R_UNLESS(freqStore && voltStore && readLoad, ldr::ResultInvalidEmcSocLut());
-
- u32 *voltBase = voltStore - 2; /* `add Xb,Xsrc,Xi,LSL#2` (a cmn sits between it and store) */
- R_UNLESS(AsmIsAddShiftedReg64(*voltBase) && asm_get_rd(*voltBase) == AsmGetRn(*voltStore),
- ldr::ResultInvalidEmcSocLut());
-
- /* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */
- const u32 lutReg = asm_get_rd(ptr[0]);
- const u32 railReg = AsmGetRn(ptr[0]);
- R_UNLESS(AsmIsAdrp(ptr[-3]) && asm_get_rd(ptr[-3]) == lutReg, ldr::ResultInvalidEmcSocLut());
- R_UNLESS(AsmIsAddImm64(ptr[-2]) && asm_get_rd(ptr[-2]) == lutReg && AsmGetRn(ptr[-2]) == lutReg,
- ldr::ResultInvalidEmcSocLut());
-
- const u32 srcBase = AsmGetRn(*voltBase); /* rail ptr at +0x20 */
- const u32 wBase = asm_get_rd(*voltBase); /* base reg */
- const u32 wIdx = AsmGetRm(*voltBase); /* loop index */
-
- PATCH_OFFSET(freqStore, NopIns); /* Unneeded */
- PATCH_OFFSET(voltBase, AsmMakeLdrImm64(wBase, srcBase, 0x20)); /* ldr Xb,[Xsrc,#0x20] (rail) */
- PATCH_OFFSET(voltStore - 1, AsmMakeAddImm64(wBase, wBase, 0x18)); /* add Xb,Xb,#0x18 (was cmn) */
- PATCH_OFFSET(voltStore, AsmSetLdStRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
- PATCH_OFFSET(voltStore + 1, NopIns); /* Unneeded */
-
- /* rail+0x18 as the socMinLut pointer. */
- PATCH_OFFSET(ptr - 3, NopIns);
- PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 0x18));/* add Xl,rail,#0x18 */
-
- /* Drop the abort branch in case of a bad read */
- for (u32 i = 1; i <= 4; ++i) {
- if (AsmIsBCond(readLoad[i])) {
- PATCH_OFFSET(&readLoad[i], NopIns);
- break;
- }
- }
- R_SUCCEED();
- }
-
- Result EmcDvfsCountLimit(u32 *ptr) {
- R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount());
-
- /* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
- PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount)));
- R_SUCCEED();
- }
-
- Result EmcRateListLimit(u32 *ptr) {
- /* ptr = cmp w?,#0x20 ; ptr[1] = csel w?,w?,w?,lt (w? = min(maxCount, 32)) ; ptr[2] = bl */
- R_UNLESS(EmcRateListPatternFn(ptr), ldr::ResultInvalidEmcRateList());
-
- /* The csel's Rm holds the 32 cap. */
- const u32 capReg = AsmGetRm(ptr[1]);
- const u32 capMov = AsmMakeMovzW(capReg, 0x20); /* movz w,#0x20 */
-
- u32 *movPtr = nullptr;
- for (u32 i = 1; i <= 16; ++i) {
- if (*(ptr - i) == capMov) {
- movPtr = ptr - i;
- break;
- }
- }
- R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
-
- /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
- PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
- PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */
- R_SUCCEED();
- }
-
- void MtcGenerateJedecTable() {
- const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
- constexpr u32 JedecFreqCount = std::size(jedecFreqs);
-
- for (u32 i = 0; i < JedecFreqCount; ++i) {
- if (jedecFreqs[i] <= C.marikoEmcMaxClock) {
- newEmcList.push_back(jedecFreqs[i]);
- } else {
- break;
- }
- }
-
- if (newEmcList.back() != C.marikoEmcMaxClock) {
- newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
- }
-
- newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
- }
-
- void MtcGenerate133StepTable() {
- const u32 stepFreqs133[] = { 1733000, 1866000, 2000000, 2133000, 2266000, 2400000, 2533000, 2666000, 2800000, 2933000, 3066000, 3200000, 3333000, 3466000, }; // Avoid rounding issues
- constexpr u32 StepFreqs133Size = std::size(stepFreqs133);
-
- for (u32 i = 0; i < StepFreqs133Size; ++i) {
- if (stepFreqs133[i] <= C.marikoEmcMaxClock) {
- newEmcList.push_back(stepFreqs133[i]);
- } else {
- break;
- }
- }
-
- if (newEmcList.back() != C.marikoEmcMaxClock) {
- newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
- }
-
- newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
- }
-
- void MtcGenerate33StepTable() {
- /* ~33.33MHz but rounded*/
- const u32 stepFreqs33[] = {
- 1633000, 1666000, 1700000, 1733000, 1766000, 1800000, 1833000, 1866000, 1900000, 1933000,
- 1966000, 2000000, 2033000, 2066000, 2100000, 2133000, 2166000, 2200000, 2233000, 2266000,
- 2300000, 2333000, 2366000, 2400000, 2433000, 2466000, 2500000, 2533000, 2566000, 2600000,
- 2633000, 2666000, 2700000, 2733000, 2766000, 2800000, 2833000, 2866000, 2900000, 2933000,
- 2966000, 3000000, 3033000, 3066000, 3100000, 3133000, 3166000, 3200000, 3233000, 3266000,
- 3300000, 3333000, 3366000, 3400000, 3433000, 3466000, 3500000,
- };
- constexpr u32 StepFreqs33Size = std::size(stepFreqs33);
-
- for (u32 i = 0; i < StepFreqs33Size; ++i) {
- if (stepFreqs33[i] <= C.marikoEmcMaxClock) {
- newEmcList.push_back(stepFreqs33[i]);
- } else {
- break;
- }
- }
-
- if (newEmcList.back() != C.marikoEmcMaxClock) {
- newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
- }
-
- newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
- }
-
- void MtcGenerateFreqTables() {
- newEmcList.clear();
- newEmcList.reserve(EmcDvfsTableEntryCount);
- newEmcList.insert(newEmcList.end(), std::begin(EmcListDefault), std::end(EmcListDefault));
-
- if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
- return;
- }
-
- u32 stepRate = 0;
- switch (C.stepMode) {
- case StepMode_33MHz:
- MtcGenerate33StepTable();
- return;
- case StepMode_66MHz:
- stepRate = 66667;
- break;
- case StepMode_100MHz:
- stepRate = 100000;
- break;
- case StepMode_Jedec:
- MtcGenerateJedecTable();
- return;
- case StepMode_133MHz:
- MtcGenerate133StepTable();
- return;
- default:
- stepRate = 66667;
- break;
- }
-
- constexpr u32 RoundHz = 1000;
- for (u32 stepIndex = 1;; ++stepIndex) {
- u32 newFreq = EmcClkOSLimit + stepIndex * stepRate;
- newFreq = (newFreq / RoundHz) * RoundHz;
- if (newFreq > C.marikoEmcMaxClock) {
- if (newEmcList.back() != C.marikoEmcMaxClock) {
- newEmcList.push_back(static_cast(C.marikoEmcMaxClock));
- }
- break;
- }
- newEmcList.push_back(newFreq);
- }
-
- newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
- }
-
- Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
- R_UNLESS(tableStart->rate_khz == expectedFreq, ldr::ResultInvalidMtcTable());
- R_UNLESS(tableStart->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable());
-
- R_SUCCEED();
- }
-
- Result MtcValidateAllTables(MarikoMtcTable *tableStart, const u32 *validationList, u32 tableCount) {
- for (u32 i = 0; i < tableCount; ++i) {
- R_TRY(VerifyMtcTable(&tableStart[i], validationList[i]));
- }
-
- R_SUCCEED();
- }
-
- DramId GetDramId() {
- u64 id64;
- splGetConfig(SplConfigItem_DramId, &id64);
- return static_cast(id64);
- }
-
- MtcTableIndex GetMtcDramIndex(DramId dramId) {
- for (u32 i = 0; i < std::size(mtcIndexTable); ++i) {
- if (mtcIndexTable[i].dramId == dramId) {
- return mtcIndexTable[i].index;
- }
- }
-
- return MtcTableIndex_Invalid;
- }
-
- NORETURN void AbortInvalidMtc(const char *crashMsg) {
- panic::SmcError(panic::Emc);
- CRASH(crashMsg);
- }
-
- u32 GetMtcOffset(MtcTableIndex index) {
- if (index < T210b0SdevEmcDvfsTableS4gb03) {
- return index * mariko::MtcFullTableSize;
- }
-
- /* There are 2 erista mtc tables between T210b0SdevEmcDvfsTableS4gb01 and T210b0SdevEmcDvfsTableS4gb03, so we have to do this adjustment. */
- return mariko::MtcFullTableSize * index + (2 * erista::MtcFullTableSize);
- }
-
- void PrepareMtcMemoryRegion(u8 *firstTable, MarikoMtcTable *usedTable) {
- memmove(firstTable, usedTable, mariko::MtcFullTableSize);
-
- /* Clear all other tables. */
- /* 1 erista table is excluded because it's always before firstTable. */
- /* We also exclude the used table obviously. */
- constexpr size_t RemainingRegionSize = (mariko::MtcFullTableSize) * (mariko::MtcFullTableCount - 1) + (erista::MtcFullTableSize * (erista::MtcFullTableCount - 1));
- memset(firstTable + mariko::MtcFullTableSize, 0, RemainingRegionSize);
- }
-
- void MtcExtendTables(MarikoMtcTable *table) {
- for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) {
- std::memcpy(&table[i], &table[i - 1], sizeof(MarikoMtcTable));
- table[i].rate_khz = newEmcList[i];
- }
- }
-
- Result MemFreqMtcTable(u32 *ptr) {
- static const DramId dramId = [] {
- DramId id = GetDramId();
- return id;
- }();
-
- static const MtcTableIndex mtcIndex = [] {
- MtcTableIndex idx = GetMtcDramIndex(dramId);
- /* If for some reason this happens, there is no chance of recovering this. */
- if (idx == MtcTableIndex_Invalid) {
- AbortInvalidMtc("Invalid dramId");
- }
- return idx;
- }();
-
- /* Offset to dram id specific mtc table. */
- static const u32 mtcOffset = GetMtcOffset(mtcIndex);
-
- /* Offset from 1600MHz pointer to 204Mhz table start. */
- constexpr u32 StartAdjustment = offsetof(MarikoMtcTable, rate_khz) + sizeof(MarikoMtcTable) * (mariko::MtcTableCountDefault - 1);
- u8 *startPtr = reinterpret_cast(ptr) - StartAdjustment;
- MarikoMtcTable *table = reinterpret_cast(startPtr + mtcOffset);
- R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault));
-
- PrepareMtcMemoryRegion(startPtr, table);
- table = reinterpret_cast(startPtr);
-
- if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) {
- AbortInvalidMtc("Failed mtc validation");
- }
-
- if (C.marikoEmcMaxClock <= EmcClkOSLimit) {
- R_SKIP();
- }
-
- MtcExtendTables(table);
-
- if (R_FAILED(MtcValidateAllTables(table, newEmcList.data(), newEmcList.size()))) {
- AbortInvalidMtc("Failed mtc validation");
- }
-
- for (u32 i = mariko::MtcTableCountDefault; i < newEmcList.size(); ++i) {
- MemMtcTableAutoAdjust(&table[i]);
- MemMtcPllmbDivisor(&table[i]);
- }
-
- R_SUCCEED();
- }
-
- Result MemFreqDvbTable(u32 *ptr) {
- DvbEntry *default_end = reinterpret_cast(ptr);
- DvbEntry *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();
- }
-
- s32 max0 = 1050;
- s32 max1 = 1025;
- s32 max2 = 1000;
-
- if (C.marikoSocVmax && C.marikoSocVmax > 1000) {
- max0 = C.marikoSocVmax;
- max1 = C.marikoSocVmax;
- max2 = C.marikoSocVmax;
- }
-
- constexpr s32 MinVolt = 637;
-
- auto ClampVolt = [&](s32 value, s32 max, s32 voltAdd) {
- return std::clamp(value + voltAdd, MinVolt, max);
- };
-
- auto DvbVolt = [&](s32 zero, s32 one, s32 two, u32 index) {
- const s32 overrideVoltage = C.marikoSocVoltArray[index];
- s32 voltAdd = 25 * C.emcDvbShift;
-
- if (overrideVoltage) {
- zero = one = two = overrideVoltage;
- voltAdd = 0;
- }
-
- return std::array{
- ClampVolt(zero, max0, voltAdd),
- ClampVolt(one, max1, voltAdd),
- ClampVolt(two, max2, voltAdd)
- };
- };
-
- #define DVB(v) \
- static_cast((v)[0]), \
- static_cast((v)[1]), \
- static_cast((v)[2])
-
- #define DVB_OC(one, zero, two, idx) \
- DVB(DvbVolt(one, zero, two, idx))
-
- DvbEntry emcDvbOcTableBrackets[] = {
- { 204000, { 637, 637, 637, }, },
- { 1331200, { 650, 637, 637, }, },
- { 1600000, { 675, 650, 637, }, },
- { 1866000, { DVB_OC( 700, 675, 650, 0) }, },
- { 2000000, { DVB_OC( 712, 687, 662, 1) }, },
- { 2133000, { DVB_OC( 725, 700, 675, 2) }, },
- { 2200000, { DVB_OC( 737, 712, 687, 3) }, },
- { 2266000, { DVB_OC( 750, 725, 700, 4) }, },
- { 2333000, { DVB_OC( 762, 737, 712, 5) }, },
- { 2400000, { DVB_OC( 775, 750, 725, 6) }, },
- { 2433000, { DVB_OC( 787, 762, 737, 7) }, },
- { 2466000, { DVB_OC( 800, 775, 750, 8) }, },
- { 2533000, { DVB_OC( 812, 787, 762, 9) }, },
- { 2566000, { DVB_OC( 825, 800, 775, 10) }, },
- { 2600000, { DVB_OC( 837, 812, 787, 11) }, },
- { 2666000, { DVB_OC( 850, 825, 800, 12) }, },
- { 2700000, { DVB_OC( 875, 850, 825, 13) }, },
- { 2733000, { DVB_OC( 887, 862, 837, 14) }, },
- { 2766000, { DVB_OC( 912, 887, 862, 15) }, },
- { 2800000, { DVB_OC( 925, 900, 875, 16) }, },
- { 2833000, { DVB_OC( 937, 912, 887, 17) }, },
- { 2900000, { DVB_OC( 950, 925, 900, 18) }, },
- { 2933000, { DVB_OC( 962, 937, 912, 19) }, },
- { 3000000, { DVB_OC( 975, 950, 925, 20) }, },
- { 3033000, { DVB_OC( 987, 962, 937, 21) }, },
- { 3100000, { DVB_OC(1000, 975, 950, 22) }, },
- { 3133000, { DVB_OC(1025, 1000, 975, 23) }, },
- { 3166000, { DVB_OC(1037, 1012, 987, 24) }, },
- { 3200000, { DVB_OC(1050, 1025, 1000, 25) }, },
- { 3266000, { DVB_OC(1075, 1050, 1025, 26) }, },
- { 3333000, { DVB_OC(1100, 1075, 1050, 27) }, },
- { ~0u, { }, },
- };
- #undef DVB
- #undef DVB_OC
-
- const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity);
- DvbEntry emcDvbTableOc[DvbTableCapacity] = {};
-
- u32 bracketIndex = 0;
- for (size_t i = 0; i < dvbCount; ++i) {
- const u32 freq = (i == dvbCount - 1) ? static_cast(newEmcList.back()) : newEmcList[i];
- while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
- ++bracketIndex;
- }
-
- emcDvbTableOc[i].freq = freq;
- std::memcpy(emcDvbTableOc[i].volt, emcDvbOcTableBrackets[bracketIndex].volt, sizeof(emcDvbTableOc[i].volt));
- }
-
- /* Clear the entire 32-entry region */
- std::memset(mem_dvb_table_head, 0, DvbTableCapacity * sizeof(DvbEntry));
- std::memcpy(mem_dvb_table_head, emcDvbTableOc, dvbCount * sizeof(DvbEntry));
-
- 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;
- R_TRY(i2cOpenSession(&_session, dev));
-
- cmd.reg = reg;
- cmd.val = val;
- Result 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();
- Result resultI2C = I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
- i2cExit();
-
- return resultI2C;
- }
-
- Result MemMtcTableAsm(u32 *ptr) {
- constexpr u32 AddpOffset = 1;
- constexpr u32 BrOffset = 12;
- constexpr u32 MovOffset = 10;
-
- /* Ensure we don't dereference memory before nso start. */
- R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
-
- u32 adrp = *(ptr - AddpOffset);
- R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
-
- /* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
- /* The correct instructions will always be x0 since the mtcTable pointer is returned. */
-
- /* Pray this does not break. */
- u32 br = *(ptr - BrOffset);
- R_UNLESS(AsmCompareBrNoRd(br, MtcBrAsm), ldr::ResultInvalidMtcTablePattern());
-
- /* Pray this does not break either. */
- u32 mov = *(ptr - MovOffset);
- R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
-
- u8 movRd = asm_get_rd(mov);
- u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), movRd);
-
- PATCH_OFFSET(ptr - BrOffset, NopIns);
- PATCH_OFFSET(ptr - MovOffset, movCountPatch);
-
- R_SUCCEED();
- }
-
- Result GetSocSpeedo(u32 &socSpeedo) {
- constexpr u64 FusePhysicalAddress = 0x7000F000;
- u64 virtualAddress = 0;
- constexpr u64 Size = 0x1000;
-
- u64 outSize;
- /* TODO: use svc::QueryMemoryMapping instead. */
- R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
-
- constexpr u32 FuseOffset = 2048;
- constexpr u32 SocSpeedoOffset = 308;
- socSpeedo = *reinterpret_cast(virtualAddress + FuseOffset + SocSpeedoOffset);
-
- R_SUCCEED();
- }
-
- u32 GetSocProcessId(u32 socSpeedo) {
- if (socSpeedo <= 1597) {
- return 0;
- }
-
- if (socSpeedo <= 1708) {
- return 1;
- }
-
- /* >= 1709. */
- return 2;
- }
-
- Result SocVoltAsm(u32 *compareSpeedos) {
- constexpr u32 VoltageScanLimit = 10;
- /* Might actually be speedo id. */
- u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd);
- R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
- u8 writeProcessIdRd = asm_get_rd(*writeProcessId);
-
- /* This writes 1050mV. */
- u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd);
- R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
- u8 writeVoltageRd = asm_get_rd(*writeVoltage);
-
- /* A csel instruction is used to select the soc voltage limit register. */
- /* We care about its destination register since that is used for verification. */
- constexpr u32 VoltageSelectScanLimit = 24;
- u32 *selectVoltage = ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, AsmCompareCselNoReg);
- R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
- /* Todo: check rm and rn? */
- u8 selectVoltageRd = asm_get_rd(*selectVoltage);
-
- /* rdCsel is then multiplied by 1000 to convert to uV. */
- /* This is pretty far down the function. */
- constexpr u32 MultiplierScanLimit = 200;
- u32 *multiplier = ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, AsmCompareMullNoReg);
- R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
- u8 multiplierRn = AsmGetMullRn(*multiplier);
- u8 multiplierRm = AsmGetMullRm(*multiplier);
- /* One of the two registers has to be rdCsel. */
- R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
- u8 multiplierRd = asm_get_rd(*multiplier);
-
- /* Subs instruction is then used to verify against absolute limit. */
- u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd);
- u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg);
- R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
-
- /* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
- u32 *branchToAbort = limitValidation + 1;
- R_UNLESS(AsmCompareBrConNoImm19(*branchToAbort, SocVoltBranchToAbortAsm), ldr::ResultInvalidSocVoltPattern());
-
- if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
- R_SKIP();
- }
-
- /* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
- /* 2200 should be high enough :D */
- u32 compareSpeedosPatch = AsmSubsSetImm12(*compareSpeedos, 2200);
- PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
-
- u32 socSpeedo = 0;
- R_TRY(GetSocSpeedo(socSpeedo));
-
- /* Adjust processId from 0 to [process id of switch booting this]. */
- /* We're overwriting the orr instruction entirly. */
- u32 processId = GetSocProcessId(socSpeedo);
- u32 writeProcessIdPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd);
- PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
-
- /* Adjust voltage limit. */
- u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd);
- PATCH_OFFSET(writeVoltage, voltageLimitPatch);
-
- /* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
- PATCH_OFFSET(branchToAbort, NopIns);
-
- R_SUCCEED();
- }
-
- Result SocVoltLimit(u32 *ptr) {
- R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
- if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
- R_SKIP();
- }
-
- constexpr u32 Step = 25;
- u32 maxVolt = C.marikoSocVmax;
- if (maxVolt % Step) {
- maxVolt = maxVolt / Step * Step; /* Round. */
- }
-
- u32 volt = SocVoltLimitOfficial;
- for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
- volt += Step;
- PATCH_OFFSET(ptr + i, volt);
- }
-
- R_SUCCEED();
- }
-
- Result EmcRateSessLimit(u32 *ptr) {
- u32 movzI = 0;
- R_UNLESS(EmcRateSessFindClamp(ptr, nullptr, nullptr, &movzI), ldr::ResultInvalidEmcRateList());
-
- /* Reject cmd11 GetDvfsTable. */
- for (u32 i = 1; i <= 24; ++i) {
- const u32 w = ptr[i];
- if (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */
- R_THROW(ldr::ResultInvalidEmcRateList());
- }
- }
-
- /* mov x,x2 */
- u32 descReg = 0xFFu;
- for (u32 i = 1; i <= 24; ++i) {
- if (AsmIsMovReg(ptr[i], 2)) { descReg = asm_get_rd(ptr[i]); break; }
- }
- R_UNLESS(descReg != 0xFFu, ldr::ResultInvalidEmcRateList());
-
- /* Repoint the duplicated-imm pair */
- u32 *adds[8]; u32 addImm[8]; u32 nAdds = 0;
- for (u32 i = 1; i <= 24 && nAdds < 8; ++i) {
- const u32 w = ptr[i];
- if (AsmIsAddSpImm(w)) { /* add x?,sp,#imm12 (shift 0) */
- adds[nAdds] = ptr + i;
- addImm[nAdds] = AsmGetImm12(w);
- ++nAdds;
- }
- }
- u32 patched = 0;
- for (u32 a = 0; a < nAdds; ++a) {
- bool dup = false;
- for (u32 b = 0; b < nAdds; ++b) {
- if (a != b && addImm[a] == addImm[b]) { dup = true; break; }
- }
- if (dup) {
- PATCH_OFFSET(adds[a], AsmMakeLdrImm64(asm_get_rd(*adds[a]), descReg, 0)); /* ldr x?,[x] */
- ++patched;
- }
- }
- R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
-
- /* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
- PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
- PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast(EmcDvfsTableEntryCount))); /* movz w?,#64 */
- R_SUCCEED();
- }
-
- void Patch(uintptr_t mapped_nso, size_t nso_size) {
- nsoStart = reinterpret_cast(mapped_nso);
-
- g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize;
- g_nso_size = nso_size;
- g_cave_cursor = g_pcv_cave; /* start the .text-cave bump allocator (0 if unavailable) */
-
- MtcGenerateFreqTables();
-
- 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 DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial },
- { "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
- { "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
- { "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
- { "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
- { "GPU Freq Table", GpuFreqCvbTable, 1, nullptr, GpuCvbDefaultMaxFreq },
- { "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
- { "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
- { "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
- { "MEM Freq Mtc", &MemFreqMtcTable, 1, 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 },
- { "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn },
- { "EMC DVFS Count", &EmcDvfsCountLimit, 1, &EmcDvfsCountPatternFn },
- { "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn },
- { "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn },
- { "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn },
- { "Bus Freq Reloc", &BusFreqReloc, 1, &BusFreqRelocPatternFn },
- { "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
- { "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
- /* Debugging patches */
- #if HOC_UART_LOG
- { "NvLog Redirect", &NvLogUartRedirect, 1, &NvLogVsnprintfPatternFn, 0, 0, true },
- { "Force Verbosity", &ForceVerbosity, 3, &ForceVerbosityPatternFn, 0, 0, true },
- #endif
- };
-
- 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())) {
- panic::SmcError(panic::Patch);
-
- CRASH(entry.description);
- }
- }
- }
-
-}