diff --git a/Source/hoc-clk/common/include/hocclk/client/ipc.h b/Source/hoc-clk/common/include/hocclk/client/ipc.h index 4f15baa4..0335492f 100644 --- a/Source/hoc-clk/common/include/hocclk/client/ipc.h +++ b/Source/hoc-clk/common/include/hocclk/client/ipc.h @@ -12,9 +12,9 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * + * */ - + /* -------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * , , @@ -50,6 +50,7 @@ Result hocclkIpcSetConfigValues(HocClkConfigValueList* configValues); Result hocclkIpcGetFreqList(HocClkModule module, u32* list, u32 maxCount, u32* outCount); Result hocClkIpcSetKipData(); Result hocClkIpcGetKipData(); +Result hocClkIpcRequestGpuVoltage(u32 voltage); static inline Result hocclkIpcRemoveOverride(HocClkModule module) { diff --git a/Source/hoc-clk/common/include/hocclk/config.h b/Source/hoc-clk/common/include/hocclk/config.h index fbcebb51..3a7a6f47 100644 --- a/Source/hoc-clk/common/include/hocclk/config.h +++ b/Source/hoc-clk/common/include/hocclk/config.h @@ -70,6 +70,7 @@ typedef enum { HocClkConfigValue_AulaDisplayColorPreset, HocClkConfigValue_MarikoMiddleFreqs, + HocClkConfigValue_LiveGpuVoltage, HocClkConfigValue_AutoRAMCPUOverclock, HocClkConfigValue_AutoRamCpuCpuOCFreq, @@ -315,6 +316,9 @@ static inline const char* hocclkFormatConfigValue(HocClkConfigValue val, bool pr case HocClkConfigValue_MarikoMiddleFreqs: return pretty ? "Mariko Middle Clocks" : "mariko_middle_freqs"; + case HocClkConfigValue_LiveGpuVoltage: + return pretty ? "GPU Voltage Request" : "gpu_voltage_request"; + case HocClkConfigValue_AutoRAMCPUOverclock: return pretty ? "Auto High RAM CPU OC" : "auto_high_ram_cpu_oc"; case HocClkConfigValue_AutoRamCpuCpuOCFreq: @@ -568,6 +572,7 @@ static inline uint64_t hocclkDefaultConfigValue(HocClkConfigValue val) case HocClkConfigValue_GPUSchedulingMethod: case HocClkConfigValue_MemoryFrequencyMeasurementMode: case HocClkConfigValue_MarikoMiddleFreqs: + case HocClkConfigValue_LiveGpuVoltage: return 0ULL; case HocClkConfigValue_RamDisplayUnit: return (uint64_t)RamDisplayUnit_MHz; @@ -625,6 +630,7 @@ static inline uint64_t hocclkValidConfigValue(HocClkConfigValue val, uint64_t in case HocClkConfigValue_LiveCpuUv: case HocClkConfigValue_GPUSchedulingMethod: case HocClkConfigValue_MarikoMiddleFreqs: + case HocClkConfigValue_LiveGpuVoltage: case HocClkConfigValue_AutoRAMCPUOverclock: return (input & 0x1) == input; diff --git a/Source/hoc-clk/common/include/hocclk/ipc.h b/Source/hoc-clk/common/include/hocclk/ipc.h index 369e1d30..bbfda90e 100644 --- a/Source/hoc-clk/common/include/hocclk/ipc.h +++ b/Source/hoc-clk/common/include/hocclk/ipc.h @@ -12,9 +12,9 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * + * */ - + /* -------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * , , @@ -50,9 +50,9 @@ enum HocClkIpcCmd HocClkIpcCmd_GetFreqList = 11, HocClkIpcCmd_SetKipData = 12, HocClkIpcCmd_GetKipData = 13, + HocClkIpcCmd_RequestGpuVoltage = 14, }; - typedef struct { uint64_t tid; diff --git a/Source/hoc-clk/common/src/client/ipc.c b/Source/hoc-clk/common/src/client/ipc.c index 6d3172b3..2fee8c1e 100644 --- a/Source/hoc-clk/common/src/client/ipc.c +++ b/Source/hoc-clk/common/src/client/ipc.c @@ -12,9 +12,9 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * + * */ - + /* -------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * , , @@ -166,4 +166,8 @@ Result hocClkIpcGetKipData() { u32 temp = 0; return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_GetKipData, temp); -} \ No newline at end of file +} + +Result hocClkIpcRequestGpuVoltage(u32 voltage) { + return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_RequestGpuVoltage, voltage); +} diff --git a/Source/hoc-clk/overlay/src/ui/gui/config_info_strings.cpp b/Source/hoc-clk/overlay/src/ui/gui/config_info_strings.cpp index fc000bc5..805009c5 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/config_info_strings.cpp +++ b/Source/hoc-clk/overlay/src/ui/gui/config_info_strings.cpp @@ -542,6 +542,15 @@ std::vector ConfigInfoStrings(HocClkConfigValue val, bool isMariko, "- Enabled: Enables GPU scheduling, 96.5% GPU max load", "Default: Do not override" }; + case HocClkConfigValue_LiveGpuVoltage: + return { + "Adjust GPU voltage on the fly.", + "Warning: unstable settings allowed.", + "Exception: while RAM OC is active, voltage cannot drop below stability minimum.", + "RAM frequency changes overwrite requested voltage.", + "Voltage applies globally across all frequencies.", + "\"Reset\" restores default or RAM-stable minimum." + }; default: return {}; } diff --git a/Source/hoc-clk/overlay/src/ui/gui/misc_gui.cpp b/Source/hoc-clk/overlay/src/ui/gui/misc_gui.cpp index bdf5a51a..a3535cfe 100644 --- a/Source/hoc-clk/overlay/src/ui/gui/misc_gui.cpp +++ b/Source/hoc-clk/overlay/src/ui/gui/misc_gui.cpp @@ -662,6 +662,9 @@ class ExperimentalSettingsSubMenuGui : public MiscGui { addConfigToggle(HocClkConfigValue_MarikoMiddleFreqs, nullptr, true); addConfigToggle(HocClkConfigValue_LiveCpuUv, nullptr); } + + addConfigToggle(HocClkConfigValue_LiveGpuVoltage, "Live GPU Volt Request", false); + std::vector gpuSchedMethodValues = { NamedValue("INI", GpuSchedulingOverrideMethod_Ini), NamedValue("NV Service", GpuSchedulingOverrideMethod_NvService), @@ -2126,6 +2129,41 @@ class GpuSubmenuGui : public MiscGui { addConfigButton(HocClkConfigValue_DVFSOffset, "GPU DVFS Offset", ValueRange(0, 12, 1, "", 0), "GPU DVFS Offset", &thresholdsDisabled, {}, dvfsOffset, false); + if (this->configList->values[HocClkConfigValue_LiveGpuVoltage]) { + std::vector gpuVoltRequestValues = { + NamedValue("Reset", 0), + }; + + for (u32 mv = IsMariko() ? 420 : 700; mv <= 1050; mv += 5) { + gpuVoltRequestValues.push_back(NamedValue(std::to_string(mv) + "mV", mv)); + } + + tsl::elm::ListItem *gpuVoltRequestBtn = new tsl::elm::ListItem("GPU Volt Request"); + gpuVoltRequestBtn->setTextColor(tsl::Color(120, 235, 255, 255)); + gpuVoltRequestBtn->setValue("Request"); + + gpuVoltRequestBtn->setClickListener([gpuVoltRequestValues](u64 keys) { + if ((keys & HidNpadButton_A) == 0) + return false; + + tsl::changeTo( + 0, ValueRange(0, 0, 1, "", 1), "GPU Volt Request", + [](u32 value) { + Result rc = hocClkIpcRequestGpuVoltage(value); + if (R_FAILED(rc)) { + FatalGui::openWithResultCode("hocClkIpcRequestGpuVoltage", rc); + return false; + } + return true; + }, + ValueThresholds(), false, std::map{}, gpuVoltRequestValues, false, false); + + return true; + }); + + this->listElement->addItem(gpuVoltRequestBtn); + } + tsl::elm::ListItem *customTableSubmenu = new tsl::elm::ListItem("GPU Voltage Table"); customTableSubmenu->setClickListener([](u64 keys) { if (keys & HidNpadButton_A) { diff --git a/Source/hoc-clk/sysmodule/src/board/board_volt.cpp b/Source/hoc-clk/sysmodule/src/board/board_volt.cpp index e37f0e63..150ad63b 100644 --- a/Source/hoc-clk/sysmodule/src/board/board_volt.cpp +++ b/Source/hoc-clk/sysmodule/src/board/board_volt.cpp @@ -335,9 +335,9 @@ namespace board { } /* 800mV on Mariko, 950mV on Erista. */ - u32 vmax = GetSocType() == HocClkSocType_Mariko ? 800 : 950; + u32 bootVoltage = GetSocType() == HocClkSocType_Mariko ? 800 : 950; constexpr u32 GpuVoltageTableOffset = 312; - if (!std::memcmp(&buffer[index + GpuVoltageTableOffset], &vmax, sizeof(vmax))) { + if (!std::memcmp(&buffer[index + GpuVoltageTableOffset], &bootVoltage, sizeof(bootVoltage))) { std::memcpy(voltData.voltTable, &buffer[index + GpuVoltageTableOffset], sizeof(voltData.voltTable)); voltData.voltTableAddress = base + memoryInfo.addr + GpuVoltageTableOffset + index; } @@ -365,18 +365,34 @@ namespace board { return; } - void PcvHijackGpuVolts(u32 vmin) { + void PcvHijackGpuVolts(u32 vmin, bool forceOverwrite) { u32 table[192]; static_assert(sizeof(table) == sizeof(voltData.voltTable), "Invalid gpu voltage table size!"); std::memcpy(table, voltData.voltTable, sizeof(voltData.voltTable)); - if (voltData.ramVmin == vmin) { + /* forceOverwrite needs to be handled regardless. */ + if (voltData.ramVmin == vmin && !forceOverwrite) { return; } - for (u32 i = 0; i < std::size(table); ++i) { - if (table[i] && table[i] <= vmin) { - table[i] = vmin; + /* Skip adjusting table if vmin is zero (voltage reset), unless forced. */ + if (vmin != 0 || forceOverwrite) { + /* Only apply forced voltage override if it's safe to do so. */ + if (forceOverwrite && vmin < voltData.ramVmin) { + vmin = voltData.ramVmin; + } + + if (vmin != 0) { + for (u32 i = 0; i < std::size(table); ++i) { + if (!table[i]) { + continue; + } + + const bool raiseVoltageRamOc = table[i] <= vmin && !forceOverwrite; + if (raiseVoltageRamOc || forceOverwrite) { + table[i] = vmin; + } + } } } @@ -388,7 +404,7 @@ namespace board { Result rc = svcWriteDebugProcessMemory(handle, table, voltData.voltTableAddress, sizeof(table)); - if (R_SUCCEEDED(rc)) { + if (R_SUCCEEDED(rc) && !forceOverwrite) { voltData.ramVmin = vmin; } diff --git a/Source/hoc-clk/sysmodule/src/board/board_volt.hpp b/Source/hoc-clk/sysmodule/src/board/board_volt.hpp index 41de0023..a35a9824 100644 --- a/Source/hoc-clk/sysmodule/src/board/board_volt.hpp +++ b/Source/hoc-clk/sysmodule/src/board/board_volt.hpp @@ -48,7 +48,7 @@ namespace board { u32 CalculateTbreak(u32 table); u32 GetVoltage(HocClkVoltage voltage); void CacheGpuVoltTable(); - void PcvHijackGpuVolts(u32 vmin); + void PcvHijackGpuVolts(u32 vmin, bool forceOverwrite); u32 GetMinimumGpuVmin(u32 freqMhz, u32 bracket); } // namespace board diff --git a/Source/hoc-clk/sysmodule/src/ipc/ipc_service.cpp b/Source/hoc-clk/sysmodule/src/ipc/ipc_service.cpp index 11a40897..95185e04 100644 --- a/Source/hoc-clk/sysmodule/src/ipc/ipc_service.cpp +++ b/Source/hoc-clk/sysmodule/src/ipc/ipc_service.cpp @@ -235,6 +235,15 @@ namespace ipcService { return 0; } break; + + case HocClkIpcCmd_RequestGpuVoltage: + if (r->data.size >= sizeof(u32)) { + u32 voltage; + memcpy(&voltage, r->data.ptr, sizeof(u32)); + clockManager::ApplyGpuVoltageRequest(voltage); + return 0; + } + break; } return HOCCLK_ERROR(Generic); diff --git a/Source/hoc-clk/sysmodule/src/mgr/clock_manager.cpp b/Source/hoc-clk/sysmodule/src/mgr/clock_manager.cpp index 69b626e3..7e18f9a4 100644 --- a/Source/hoc-clk/sysmodule/src/mgr/clock_manager.cpp +++ b/Source/hoc-clk/sysmodule/src/mgr/clock_manager.cpp @@ -332,6 +332,28 @@ namespace clockManager { } } + u32 ClampGpuVoltage(u32 voltage) { + constexpr u32 MaxGpuVoltage = 960; + return std::min(voltage, MaxGpuVoltage); + } + + u32 GetCurrentNearestGpuFrequency() { + u32 gpuHz = board::GetHz(HocClkModule_GPU); + u32 maxHz = GetMaxAllowedHz(HocClkModule_GPU, gContext.profile); + return GetNearestHz(HocClkModule_GPU, gpuHz, maxHz); + } + + void ApplyGpuVoltageRequest(u32 voltage) { + fileUtils::LogLine("Unclamped voltage request: %u", voltage); + voltage = ClampGpuVoltage(voltage); + fileUtils::LogLine("Actual voltage: %u", voltage); + + board::PcvHijackGpuVolts(voltage, true); + + /* Update the voltage using the currently nearest valid gpu frequency. */ + board::SetHz(HocClkModule_GPU, GetCurrentNearestGpuFrequency()); + } + void ApplyGpuDvfs(u32 targetHz) { s32 dvfsOffset = config::GetConfigValue(HocClkConfigValue_DVFSOffset); dvfsOffset = std::max(dvfsOffset, -80); @@ -342,19 +364,14 @@ namespace clockManager { } /* Prevent console from combusting if for some reason bad shit happens :P */ - vmin = std::min(vmin, 1000u); - - /* Get nearest gpu clock; we need this in a second to update the voltage. */ - u32 gpuHz = board::GetHz(HocClkModule_GPU); - u32 maxHz = GetMaxAllowedHz(HocClkModule_GPU, gContext.profile); - u32 nearestGpuHz = GetNearestHz(HocClkModule_GPU, gpuHz, maxHz); + vmin = ClampGpuVoltage(vmin); /* Hijack gpu volt table. */ - board::PcvHijackGpuVolts(vmin); + board::PcvHijackGpuVolts(vmin, false); /* Update gpu frequency to actually use the voltage. */ if (targetHz) { - board::SetHz(HocClkModule_GPU, nearestGpuHz); + board::SetHz(HocClkModule_GPU, GetCurrentNearestGpuFrequency()); } else { /* If the target frequency is zero, we reset the frequency to ensure it gets updated even without any frequency override. */ board::ResetToStockGpu(); @@ -363,7 +380,7 @@ namespace clockManager { void DVFSReset() { if (config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) { - board::PcvHijackGpuVolts(0); // Reset to vMin + board::PcvHijackGpuVolts(0, false); // Reset to vMin u32 targetHz = gContext.overrideFreqs[HocClkModule_GPU]; if (!targetHz) { @@ -515,6 +532,7 @@ namespace clockManager { if (module == HocClkModule_MEM && targetHz > oldHz && config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) { ApplyGpuDvfs(targetHz); } + board::SetHz((HocClkModule)module, nearestHz); gContext.freqs[module] = nearestHz; @@ -560,7 +578,7 @@ namespace clockManager { if (hasChanged) { board::ResetToStock(); if (config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) { - board::PcvHijackGpuVolts(0); + board::PcvHijackGpuVolts(0, false); board::ResetToStockGpu(); } WaitForNextTick(); diff --git a/Source/hoc-clk/sysmodule/src/mgr/clock_manager.hpp b/Source/hoc-clk/sysmodule/src/mgr/clock_manager.hpp index 8235fbbc..7e9e027d 100644 --- a/Source/hoc-clk/sysmodule/src/mgr/clock_manager.hpp +++ b/Source/hoc-clk/sysmodule/src/mgr/clock_manager.hpp @@ -63,6 +63,8 @@ namespace clockManager { void GetFreqList(HocClkModule module, std::uint32_t *list, std::uint32_t maxCount, std::uint32_t *outCount); + void ApplyGpuVoltageRequest(u32 voltage); + void Tick(); void WaitForNextTick(); } // namespace clockManager