add gpu voltage request

This commit is contained in:
Lightos1
2026-07-04 13:44:45 +02:00
parent d7df43e962
commit cb0ffbcd29
11 changed files with 130 additions and 27 deletions

View File

@@ -12,9 +12,9 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42): * "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net> * <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
@@ -50,6 +50,7 @@ Result hocclkIpcSetConfigValues(HocClkConfigValueList* configValues);
Result hocclkIpcGetFreqList(HocClkModule module, u32* list, u32 maxCount, u32* outCount); Result hocclkIpcGetFreqList(HocClkModule module, u32* list, u32 maxCount, u32* outCount);
Result hocClkIpcSetKipData(); Result hocClkIpcSetKipData();
Result hocClkIpcGetKipData(); Result hocClkIpcGetKipData();
Result hocClkIpcRequestGpuVoltage(u32 voltage);
static inline Result hocclkIpcRemoveOverride(HocClkModule module) static inline Result hocclkIpcRemoveOverride(HocClkModule module)
{ {

View File

@@ -70,6 +70,7 @@ typedef enum {
HocClkConfigValue_AulaDisplayColorPreset, HocClkConfigValue_AulaDisplayColorPreset,
HocClkConfigValue_MarikoMiddleFreqs, HocClkConfigValue_MarikoMiddleFreqs,
HocClkConfigValue_LiveGpuVoltage,
HocClkConfigValue_AutoRAMCPUOverclock, HocClkConfigValue_AutoRAMCPUOverclock,
HocClkConfigValue_AutoRamCpuCpuOCFreq, HocClkConfigValue_AutoRamCpuCpuOCFreq,
@@ -315,6 +316,9 @@ static inline const char* hocclkFormatConfigValue(HocClkConfigValue val, bool pr
case HocClkConfigValue_MarikoMiddleFreqs: case HocClkConfigValue_MarikoMiddleFreqs:
return pretty ? "Mariko Middle Clocks" : "mariko_middle_freqs"; return pretty ? "Mariko Middle Clocks" : "mariko_middle_freqs";
case HocClkConfigValue_LiveGpuVoltage:
return pretty ? "GPU Voltage Request" : "gpu_voltage_request";
case HocClkConfigValue_AutoRAMCPUOverclock: case HocClkConfigValue_AutoRAMCPUOverclock:
return pretty ? "Auto High RAM CPU OC" : "auto_high_ram_cpu_oc"; return pretty ? "Auto High RAM CPU OC" : "auto_high_ram_cpu_oc";
case HocClkConfigValue_AutoRamCpuCpuOCFreq: case HocClkConfigValue_AutoRamCpuCpuOCFreq:
@@ -568,6 +572,7 @@ static inline uint64_t hocclkDefaultConfigValue(HocClkConfigValue val)
case HocClkConfigValue_GPUSchedulingMethod: case HocClkConfigValue_GPUSchedulingMethod:
case HocClkConfigValue_MemoryFrequencyMeasurementMode: case HocClkConfigValue_MemoryFrequencyMeasurementMode:
case HocClkConfigValue_MarikoMiddleFreqs: case HocClkConfigValue_MarikoMiddleFreqs:
case HocClkConfigValue_LiveGpuVoltage:
return 0ULL; return 0ULL;
case HocClkConfigValue_RamDisplayUnit: case HocClkConfigValue_RamDisplayUnit:
return (uint64_t)RamDisplayUnit_MHz; return (uint64_t)RamDisplayUnit_MHz;
@@ -625,6 +630,7 @@ static inline uint64_t hocclkValidConfigValue(HocClkConfigValue val, uint64_t in
case HocClkConfigValue_LiveCpuUv: case HocClkConfigValue_LiveCpuUv:
case HocClkConfigValue_GPUSchedulingMethod: case HocClkConfigValue_GPUSchedulingMethod:
case HocClkConfigValue_MarikoMiddleFreqs: case HocClkConfigValue_MarikoMiddleFreqs:
case HocClkConfigValue_LiveGpuVoltage:
case HocClkConfigValue_AutoRAMCPUOverclock: case HocClkConfigValue_AutoRAMCPUOverclock:
return (input & 0x1) == input; return (input & 0x1) == input;

View File

@@ -12,9 +12,9 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42): * "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net> * <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
@@ -50,9 +50,9 @@ enum HocClkIpcCmd
HocClkIpcCmd_GetFreqList = 11, HocClkIpcCmd_GetFreqList = 11,
HocClkIpcCmd_SetKipData = 12, HocClkIpcCmd_SetKipData = 12,
HocClkIpcCmd_GetKipData = 13, HocClkIpcCmd_GetKipData = 13,
HocClkIpcCmd_RequestGpuVoltage = 14,
}; };
typedef struct typedef struct
{ {
uint64_t tid; uint64_t tid;

View File

@@ -12,9 +12,9 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42): * "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net> * <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
@@ -166,4 +166,8 @@ Result hocClkIpcGetKipData()
{ {
u32 temp = 0; u32 temp = 0;
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_GetKipData, temp); return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_GetKipData, temp);
} }
Result hocClkIpcRequestGpuVoltage(u32 voltage) {
return serviceDispatchIn(&g_hocclkSrv, HocClkIpcCmd_RequestGpuVoltage, voltage);
}

View File

@@ -542,6 +542,15 @@ std::vector<std::string> ConfigInfoStrings(HocClkConfigValue val, bool isMariko,
"- Enabled: Enables GPU scheduling, 96.5% GPU max load", "- Enabled: Enables GPU scheduling, 96.5% GPU max load",
"Default: Do not override" "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: default:
return {}; return {};
} }

View File

@@ -662,6 +662,9 @@ class ExperimentalSettingsSubMenuGui : public MiscGui {
addConfigToggle(HocClkConfigValue_MarikoMiddleFreqs, nullptr, true); addConfigToggle(HocClkConfigValue_MarikoMiddleFreqs, nullptr, true);
addConfigToggle(HocClkConfigValue_LiveCpuUv, nullptr); addConfigToggle(HocClkConfigValue_LiveCpuUv, nullptr);
} }
addConfigToggle(HocClkConfigValue_LiveGpuVoltage, "Live GPU Volt Request", false);
std::vector<NamedValue> gpuSchedMethodValues = { std::vector<NamedValue> gpuSchedMethodValues = {
NamedValue("INI", GpuSchedulingOverrideMethod_Ini), NamedValue("INI", GpuSchedulingOverrideMethod_Ini),
NamedValue("NV Service", GpuSchedulingOverrideMethod_NvService), 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, {}, addConfigButton(HocClkConfigValue_DVFSOffset, "GPU DVFS Offset", ValueRange(0, 12, 1, "", 0), "GPU DVFS Offset", &thresholdsDisabled, {},
dvfsOffset, false); dvfsOffset, false);
if (this->configList->values[HocClkConfigValue_LiveGpuVoltage]) {
std::vector<NamedValue> 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<ValueChoiceGui>(
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<u32, std::string>{}, gpuVoltRequestValues, false, false);
return true;
});
this->listElement->addItem(gpuVoltRequestBtn);
}
tsl::elm::ListItem *customTableSubmenu = new tsl::elm::ListItem("GPU Voltage Table"); tsl::elm::ListItem *customTableSubmenu = new tsl::elm::ListItem("GPU Voltage Table");
customTableSubmenu->setClickListener([](u64 keys) { customTableSubmenu->setClickListener([](u64 keys) {
if (keys & HidNpadButton_A) { if (keys & HidNpadButton_A) {

View File

@@ -335,9 +335,9 @@ namespace board {
} }
/* 800mV on Mariko, 950mV on Erista. */ /* 800mV on Mariko, 950mV on Erista. */
u32 vmax = GetSocType() == HocClkSocType_Mariko ? 800 : 950; u32 bootVoltage = GetSocType() == HocClkSocType_Mariko ? 800 : 950;
constexpr u32 GpuVoltageTableOffset = 312; 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)); std::memcpy(voltData.voltTable, &buffer[index + GpuVoltageTableOffset], sizeof(voltData.voltTable));
voltData.voltTableAddress = base + memoryInfo.addr + GpuVoltageTableOffset + index; voltData.voltTableAddress = base + memoryInfo.addr + GpuVoltageTableOffset + index;
} }
@@ -365,18 +365,34 @@ namespace board {
return; return;
} }
void PcvHijackGpuVolts(u32 vmin) { void PcvHijackGpuVolts(u32 vmin, bool forceOverwrite) {
u32 table[192]; u32 table[192];
static_assert(sizeof(table) == sizeof(voltData.voltTable), "Invalid gpu voltage table size!"); static_assert(sizeof(table) == sizeof(voltData.voltTable), "Invalid gpu voltage table size!");
std::memcpy(table, voltData.voltTable, sizeof(voltData.voltTable)); std::memcpy(table, voltData.voltTable, sizeof(voltData.voltTable));
if (voltData.ramVmin == vmin) { /* forceOverwrite needs to be handled regardless. */
if (voltData.ramVmin == vmin && !forceOverwrite) {
return; return;
} }
for (u32 i = 0; i < std::size(table); ++i) { /* Skip adjusting table if vmin is zero (voltage reset), unless forced. */
if (table[i] && table[i] <= vmin) { if (vmin != 0 || forceOverwrite) {
table[i] = vmin; /* 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)); Result rc = svcWriteDebugProcessMemory(handle, table, voltData.voltTableAddress, sizeof(table));
if (R_SUCCEEDED(rc)) { if (R_SUCCEEDED(rc) && !forceOverwrite) {
voltData.ramVmin = vmin; voltData.ramVmin = vmin;
} }

View File

@@ -48,7 +48,7 @@ namespace board {
u32 CalculateTbreak(u32 table); u32 CalculateTbreak(u32 table);
u32 GetVoltage(HocClkVoltage voltage); u32 GetVoltage(HocClkVoltage voltage);
void CacheGpuVoltTable(); void CacheGpuVoltTable();
void PcvHijackGpuVolts(u32 vmin); void PcvHijackGpuVolts(u32 vmin, bool forceOverwrite);
u32 GetMinimumGpuVmin(u32 freqMhz, u32 bracket); u32 GetMinimumGpuVmin(u32 freqMhz, u32 bracket);
} // namespace board } // namespace board

View File

@@ -235,6 +235,15 @@ namespace ipcService {
return 0; return 0;
} }
break; 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); return HOCCLK_ERROR(Generic);

View File

@@ -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) { void ApplyGpuDvfs(u32 targetHz) {
s32 dvfsOffset = config::GetConfigValue(HocClkConfigValue_DVFSOffset); s32 dvfsOffset = config::GetConfigValue(HocClkConfigValue_DVFSOffset);
dvfsOffset = std::max(dvfsOffset, -80); dvfsOffset = std::max(dvfsOffset, -80);
@@ -342,19 +364,14 @@ namespace clockManager {
} }
/* Prevent console from combusting if for some reason bad shit happens :P */ /* Prevent console from combusting if for some reason bad shit happens :P */
vmin = std::min(vmin, 1000u); vmin = ClampGpuVoltage(vmin);
/* 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);
/* Hijack gpu volt table. */ /* Hijack gpu volt table. */
board::PcvHijackGpuVolts(vmin); board::PcvHijackGpuVolts(vmin, false);
/* Update gpu frequency to actually use the voltage. */ /* Update gpu frequency to actually use the voltage. */
if (targetHz) { if (targetHz) {
board::SetHz(HocClkModule_GPU, nearestGpuHz); board::SetHz(HocClkModule_GPU, GetCurrentNearestGpuFrequency());
} else { } else {
/* If the target frequency is zero, we reset the frequency to ensure it gets updated even without any frequency override. */ /* If the target frequency is zero, we reset the frequency to ensure it gets updated even without any frequency override. */
board::ResetToStockGpu(); board::ResetToStockGpu();
@@ -363,7 +380,7 @@ namespace clockManager {
void DVFSReset() { void DVFSReset() {
if (config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) { 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]; u32 targetHz = gContext.overrideFreqs[HocClkModule_GPU];
if (!targetHz) { if (!targetHz) {
@@ -515,6 +532,7 @@ namespace clockManager {
if (module == HocClkModule_MEM && targetHz > oldHz && config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) { if (module == HocClkModule_MEM && targetHz > oldHz && config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) {
ApplyGpuDvfs(targetHz); ApplyGpuDvfs(targetHz);
} }
board::SetHz((HocClkModule)module, nearestHz); board::SetHz((HocClkModule)module, nearestHz);
gContext.freqs[module] = nearestHz; gContext.freqs[module] = nearestHz;
@@ -560,7 +578,7 @@ namespace clockManager {
if (hasChanged) { if (hasChanged) {
board::ResetToStock(); board::ResetToStock();
if (config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) { if (config::GetConfigValue(HocClkConfigValue_DVFSMode) == DVFSMode_Hijack) {
board::PcvHijackGpuVolts(0); board::PcvHijackGpuVolts(0, false);
board::ResetToStockGpu(); board::ResetToStockGpu();
} }
WaitForNextTick(); WaitForNextTick();

View File

@@ -63,6 +63,8 @@ namespace clockManager {
void GetFreqList(HocClkModule module, std::uint32_t *list, std::uint32_t maxCount, std::uint32_t *outCount); void GetFreqList(HocClkModule module, std::uint32_t *list, std::uint32_t maxCount, std::uint32_t *outCount);
void ApplyGpuVoltageRequest(u32 voltage);
void Tick(); void Tick();
void WaitForNextTick(); void WaitForNextTick();
} // namespace clockManager } // namespace clockManager