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