/* * 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 . * */ /* -------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * , , * wrote this file. As long as you retain this notice you can do whatever you * want with this stuff. If you meet any of us some day, and you think this * stuff is worth it, you can buy us a beer in return. - The sys-clk authors * -------------------------------------------------------------------------- */ // Note: Hoag crashes on display refresh rate init while in sleep mode #include #include "board.h" #include "errors.h" #include "rgltr.h" #include "file_utils.h" #include // for std::clamp #include #include #include #include #include #include #include #include #include #include #include #define MAX(A, B) std::max(A, B) #define MIN(A, B) std::min(A, B) #define CEIL(A) std::ceil(A) #define FLOOR(A) std::floor(A) #define ROUND(A) std::lround(A) #define FUSE_CPU_SPEEDO_0_CALIB 0x114 //#define FUSE_CPU_SPEEDO_1_CALIB 0x12C #define FUSE_CPU_SPEEDO_2_CALIB 0x130 #define FUSE_SOC_SPEEDO_0_CALIB 0x134 //#define FUSE_SOC_SPEEDO_1_CALIB 0x138 //#define FUSE_SOC_SPEEDO_2_CALIB 0x13C #define FUSE_CPU_IDDQ_CALIB 0x118 #define FUSE_SOC_IDDQ_CALIB 0x140 #define FUSE_GPU_IDDQ_CALIB 0x228 #define HOSSVC_HAS_CLKRST (hosversionAtLeast(8,0,0)) #define HOSSVC_HAS_TC (hosversionAtLeast(5,0,0)) #define NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD 0x80044715 #define NVSCHED_CTRL_ENABLE 0x00000601 #define NVSCHED_CTRL_DISABLE 0x00000602 constexpr u64 CpuTimeOutNs = 500'000'000; constexpr double Systemtickfrequency = 19200000.0 * (static_cast(CpuTimeOutNs) / 1'000'000'000.0); Result nvInitialize_rc; Result nvCheck = 1; Result nvCheck_sched = 1; LEvent threadexit; Thread gpuLThread; Thread cpuCore0Thread; Thread cpuCore1Thread; Thread cpuCore2Thread; Thread cpuCore3Thread; Thread miscThread; double temp = 0; PwmChannelSession g_ICon; Result pwmCheck = 1; Result pwmDutyCycleCheck = 1; double Rotation_Duty = 0; u8 fanLevel; uint32_t GPU_Load_u = 0, fd = 0, fd2 = 0; BatteryChargeInfo info; static SysClkSocType g_socType = SysClkSocType_Erista; static HorizonOCConsoleType g_consoleType = HorizonOCConsoleType_Iowa; u64 idletick0 = 0; u64 idletick1 = 0; u64 idletick2 = 0; // u64 idletick3 = 0; u32 cpu0, cpu1, cpu2, cpu3, cpuAvg; u16 cpuSpeedo0, cpuSpeedo2, socSpeedo0; // CPU, GPU, SOC u32 speedoBracket; u16 cpuIDDQ, gpuIDDQ, socIDDQ; u8 g_dramID = 0; u64 cldvfs, cldvfs_temp; u32 cachedEristaUvLowTune0 = 0, cachedEristaUvLowTune1 = 0, cachedMarikoUvHighTune0 = 0; static const u32 ramBrackets[][22] = { { 2133, 2200, 2266, 2300, 2366, 2400, 2433, 2466, 2533, 2566, 2600, 2633, 2700, 2733, 2766, 2833, 2866, 2900, 2933, 3033, 3066, 3100, }, { 2300, 2366, 2433, 2466, 2533, 2566, 2633, 2700, 2733, 2800, 2833, 2900, 2933, 2966, 3033, 3066, 3100, 3133, 3166, 3200, 3233, 3266, }, { 2433, 2466, 2533, 2600, 2666, 2733, 2766, 2800, 2833, 2866, 2933, 2966, 3033, 3066, 3100, 3133, 3166, 3200, 3233, 3300, 3333, 3366, }, { 2500, 2533, 2600, 2633, 2666, 2733, 2800, 2866, 2900, 2966, 3033, 3100, 3166, 3200, 3233, 3266, 3300, 3333, 3366, 3400, 3400, 3400, }, }; static const u32 gpuDvfsArray[] = { 590, 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, 750, 760, 770, 780, 790, 800}; u32 dvfsTable[6][32] = {}; u64 dvfsAddress; u32 ramVmin; const char* Board::GetModuleName(SysClkModule module, bool pretty) { ASSERT_ENUM_VALID(SysClkModule, module); return sysclkFormatModule(module, pretty); } const char* Board::GetProfileName(SysClkProfile profile, bool pretty) { ASSERT_ENUM_VALID(SysClkProfile, profile); return sysclkFormatProfile(profile, pretty); } const char* Board::GetThermalSensorName(SysClkThermalSensor sensor, bool pretty) { ASSERT_ENUM_VALID(SysClkThermalSensor, sensor); return sysclkFormatThermalSensor(sensor, pretty); } const char* Board::GetPowerSensorName(SysClkPowerSensor sensor, bool pretty) { ASSERT_ENUM_VALID(SysClkPowerSensor, sensor); return sysclkFormatPowerSensor(sensor, pretty); } PcvModule Board::GetPcvModule(SysClkModule sysclkModule) { switch(sysclkModule) { case SysClkModule_CPU: return PcvModule_CpuBus; case SysClkModule_GPU: return PcvModule_GPU; case SysClkModule_MEM: return PcvModule_EMC; default: ASSERT_ENUM_VALID(SysClkModule, sysclkModule); } return (PcvModule)0; } PcvModuleId Board::GetPcvModuleId(SysClkModule sysclkModule) { PcvModuleId pcvModuleId; Result rc = pcvGetModuleId(&pcvModuleId, GetPcvModule(sysclkModule)); ASSERT_RESULT_OK(rc, "pcvGetModuleId"); return pcvModuleId; } void CheckCore(void *idletickPtr) { u64* idletick = static_cast(idletickPtr); while(true) { u64 idletickA; u64 idletickB; svcGetInfo(&idletickB, InfoType_IdleTickCount, INVALID_HANDLE, -1); svcWaitForAddress(&threadexit, ArbitrationType_WaitIfEqual, 0, CpuTimeOutNs); svcGetInfo(&idletickA, InfoType_IdleTickCount, INVALID_HANDLE, -1); *idletick = idletickA - idletickB; } } void gpuLoadThread(void*) { #define gpu_samples_average 8 uint32_t gpu_load_array[gpu_samples_average] = {0}; size_t i = 0; if (R_SUCCEEDED(nvCheck)) do { u32 temp; if (R_SUCCEEDED(nvIoctl(fd, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &temp))) { gpu_load_array[i++ % gpu_samples_average] = temp; GPU_Load_u = std::accumulate(&gpu_load_array[0], &gpu_load_array[gpu_samples_average], 0) / gpu_samples_average; } svcSleepThread(16'666'000); // wait a bit (this is the perfect amount of time to keep the reading accurate) } while(true); } void miscThreadFunc(void*) { for(;;) { if (R_SUCCEEDED(pwmCheck)) { if (R_SUCCEEDED(pwmChannelSessionGetDutyCycle(&g_ICon, &temp))) { temp *= 10; temp = trunc(temp); temp /= 10; Rotation_Duty = 100.0 - temp; } } fanLevel = (u8)Rotation_Duty; svcSleepThread(300'000'000); } } void Board::Initialize() { Result rc = 0; if(HOSSVC_HAS_CLKRST) { rc = clkrstInitialize(); ASSERT_RESULT_OK(rc, "clkrstInitialize"); } else { rc = pcvInitialize(); ASSERT_RESULT_OK(rc, "pcvInitialize"); } rc = apmExtInitialize(); ASSERT_RESULT_OK(rc, "apmExtInitialize"); rc = psmInitialize(); ASSERT_RESULT_OK(rc, "psmInitialize"); if(HOSSVC_HAS_TC) { rc = tcInitialize(); ASSERT_RESULT_OK(rc, "tcInitialize"); } rc = max17050Initialize(); ASSERT_RESULT_OK(rc, "max17050Initialize"); rc = tmp451Initialize(); ASSERT_RESULT_OK(rc, "tmp451Initialize"); nvInitialize_rc = nvInitialize(); if (R_SUCCEEDED(nvInitialize_rc)) { nvCheck = nvOpen(&fd, "/dev/nvhost-ctrl-gpu"); nvCheck_sched = nvOpen(&fd2, "/dev/nvsched-ctrl"); } rc = rgltrInitialize(); ASSERT_RESULT_OK(rc, "rgltrInitialize"); // if (R_SUCCEEDED(fanInitialize())) { // if (hosversionAtLeast(7,0,0)) fanCheck = fanOpenController(&fanController, 0x3D000001); // else fanCheck = fanOpenController(&fanController, 1); // } rc = pmdmntInitialize(); ASSERT_RESULT_OK(rc, "pmdmntInitialize"); threadCreate(&gpuLThread, gpuLoadThread, NULL, NULL, 0x1000, 0x3F, -2); threadStart(&gpuLThread); leventClear(&threadexit); threadCreate(&cpuCore0Thread, CheckCore, &idletick0, NULL, 0x1000, 0x10, 0); threadCreate(&cpuCore1Thread, CheckCore, &idletick1, NULL, 0x1000, 0x10, 1); threadCreate(&cpuCore2Thread, CheckCore, &idletick2, NULL, 0x1000, 0x10, 2); // threadCreate(&cpuCore3Thread, CheckCore, &idletick3, NULL, 0x1000, 0x10, 3); threadCreate(&miscThread, miscThreadFunc, NULL, NULL, 0x1000, 0x10, 3); threadStart(&cpuCore0Thread); threadStart(&cpuCore1Thread); threadStart(&cpuCore2Thread); // threadStart(&cpuCore3Thread); threadStart(&miscThread); batteryInfoInitialize(); if (hosversionAtLeast(6,0,0) && R_SUCCEEDED(pwmInitialize())) { pwmCheck = pwmOpenSession2(&g_ICon, 0x3D000001); } if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) { u64 clkVirtAddr, dsiVirtAddr, outsize; rc = svcQueryMemoryMapping(&clkVirtAddr, &outsize, 0x60006000, 0x1000); ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (clk)"); rc = svcQueryMemoryMapping(&dsiVirtAddr, &outsize, 0x54300000, 0x40000); ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (dsi)"); DisplayRefreshConfig cfg = {.clkVirtAddr = clkVirtAddr, .dsiVirtAddr = dsiVirtAddr}; DisplayRefresh_Initialize(&cfg); } FetchHardwareInfos(); rc = svcQueryMemoryMapping(&cldvfs, &cldvfs_temp, CLDVFS_REGION_BASE, CLDVFS_REGION_SIZE); ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (cldvfs)"); if(Board::GetSocType() == SysClkSocType_Erista) { cachedEristaUvLowTune0 = *(u32*)(cldvfs + CL_DVFS_TUNE0_0); cachedEristaUvLowTune1 = *(u32*)(cldvfs + CL_DVFS_TUNE1_0); } else { Board::SetHz(SysClkModule_CPU, 1785000000); cachedMarikoUvHighTune0 = *(u32*)(cldvfs + CL_DVFS_TUNE0_0); Board::ResetToStockCpu(); } } void Board::fuseReadSpeedos() { u64 pid = 0; if (R_FAILED(pmdmntGetProcessId(&pid, 0x0100000000000006))) { return; } Handle debug; if (R_FAILED(svcDebugActiveProcess(&debug, pid))) { return; } MemoryInfo mem_info = {0}; u32 pageinfo = 0; u64 addr = 0; char stack[0x10] = {0}; const char compare[0x10] = {0}; char dump[0x400] = {0}; while (true) { if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &pageinfo, debug, addr)) || mem_info.addr < addr) { break; } if (mem_info.type == MemType_Io && mem_info.size == 0x1000) { if (R_FAILED(svcReadDebugProcessMemory(stack, debug, mem_info.addr, sizeof(stack)))) { break; } if (memcmp(stack, compare, sizeof(stack)) == 0) { if (R_FAILED(svcReadDebugProcessMemory(dump, debug, mem_info.addr + 0x800, sizeof(dump)))) { break; } cpuSpeedo0 = *reinterpret_cast(dump + FUSE_CPU_SPEEDO_0_CALIB); cpuSpeedo2 = *reinterpret_cast(dump + FUSE_CPU_SPEEDO_2_CALIB); socSpeedo0 = *reinterpret_cast(dump + FUSE_SOC_SPEEDO_0_CALIB); cpuIDDQ = *reinterpret_cast(dump + FUSE_CPU_IDDQ_CALIB); gpuIDDQ = *reinterpret_cast(dump + FUSE_SOC_IDDQ_CALIB); socIDDQ = *reinterpret_cast(dump + FUSE_GPU_IDDQ_CALIB); svcCloseHandle(debug); return; } } addr = mem_info.addr + mem_info.size; } svcCloseHandle(debug); } u16 Board::getSpeedo(HorizonOCSpeedo speedoType) { switch(speedoType) { case HorizonOCSpeedo_CPU: return cpuSpeedo0; case HorizonOCSpeedo_GPU: return cpuSpeedo2; case HorizonOCSpeedo_SOC: return socSpeedo0; default: ASSERT_ENUM_VALID(HorizonOCSpeedo, speedoType); return 0; } } u16 Board::getIDDQ(HorizonOCSpeedo speedoType) { switch(speedoType) { case HorizonOCSpeedo_CPU: return cpuIDDQ; case HorizonOCSpeedo_GPU: return gpuIDDQ; case HorizonOCSpeedo_SOC: return socIDDQ; default: ASSERT_ENUM_VALID(HorizonOCSpeedo, speedoType); return 0; } } void Board::Exit() { if(HOSSVC_HAS_CLKRST) { clkrstExit(); } else { pcvExit(); } apmExtExit(); psmExit(); if(HOSSVC_HAS_TC) { tcExit(); } max17050Exit(); tmp451Exit(); threadClose(&gpuLThread); threadClose(&cpuCore0Thread); threadClose(&cpuCore1Thread); threadClose(&cpuCore2Thread); // threadClose(&cpuCore3Thread); threadClose(&miscThread); pwmChannelSessionClose(&g_ICon); pwmExit(); rgltrExit(); batteryInfoExit(); pmdmntExit(); nvExit(); if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) DisplayRefresh_Shutdown(); } SysClkProfile Board::GetProfile() { std::uint32_t mode = 0; Result rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); if(mode) { return SysClkProfile_Docked; } PsmChargerType chargerType; rc = psmGetChargerType(&chargerType); ASSERT_RESULT_OK(rc, "psmGetChargerType"); if(chargerType == PsmChargerType_EnoughPower) { return SysClkProfile_HandheldChargingOfficial; } else if(chargerType == PsmChargerType_LowPower) { return SysClkProfile_HandheldChargingUSB; } return SysClkProfile_Handheld; } void Board::SetHz(SysClkModule module, std::uint32_t hz) { Result rc = 0; if(module == HorizonOCModule_Display && Board::GetConsoleType() != HorizonOCConsoleType_Hoag) { DisplayRefresh_SetRate(hz); return; } if(module > SysClkModule_MEM) return; if(HOSSVC_HAS_CLKRST) { ClkrstSession session = {0}; rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); ASSERT_RESULT_OK(rc, "clkrstOpenSession"); rc = clkrstSetClockRate(&session, hz); ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); if (module == SysClkModule_CPU) { svcSleepThread(200'000); rc = clkrstSetClockRate(&session, hz); ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); } clkrstCloseSession(&session); } else { rc = pcvSetClockRate(Board::GetPcvModule(module), hz); ASSERT_RESULT_OK(rc, "pcvSetClockRate"); if (module == SysClkModule_CPU) { svcSleepThread(200'000); rc = pcvSetClockRate(Board::GetPcvModule(module), hz); ASSERT_RESULT_OK(rc, "pcvSetClockRate"); } } } std::uint32_t Board::GetHz(SysClkModule module) { Result rc = 0; std::uint32_t hz = 0; if(module == HorizonOCModule_Display) { if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) DisplayRefresh_GetRate(&hz, false); else hz = 60; return hz; } if(HOSSVC_HAS_CLKRST) { ClkrstSession session = {0}; rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); ASSERT_RESULT_OK(rc, "clkrstOpenSession"); rc = clkrstGetClockRate(&session, &hz); ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); clkrstCloseSession(&session); } else { rc = pcvGetClockRate(Board::GetPcvModule(module), &hz); ASSERT_RESULT_OK(rc, "pcvGetClockRate"); } return hz; } std::uint32_t Board::GetRealHz(SysClkModule module) { u32 hz = 0; switch(module) { case SysClkModule_CPU: return t210ClkCpuFreq(); case SysClkModule_GPU: return t210ClkGpuFreq(); case SysClkModule_MEM: return t210ClkMemFreq(); case HorizonOCModule_Display: if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) DisplayRefresh_GetRate(&hz, false); else hz = 60; return hz; default: ASSERT_ENUM_VALID(SysClkModule, module); } return 0; } void Board::GetFreqList(SysClkModule module, std::uint32_t* outList, std::uint32_t maxCount, std::uint32_t* outCount) { Result rc = 0; PcvClockRatesListType type; s32 tmpInMaxCount = maxCount; s32 tmpOutCount = 0; if(HOSSVC_HAS_CLKRST) { ClkrstSession session = {0}; rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); ASSERT_RESULT_OK(rc, "clkrstOpenSession"); rc = clkrstGetPossibleClockRates(&session, outList, tmpInMaxCount, &type, &tmpOutCount); ASSERT_RESULT_OK(rc, "clkrstGetPossibleClockRates"); clkrstCloseSession(&session); } else { rc = pcvGetPossibleClockRates(Board::GetPcvModule(module), outList, tmpInMaxCount, &type, &tmpOutCount); ASSERT_RESULT_OK(rc, "pcvGetPossibleClockRates"); } if(type != PcvClockRatesListType_Discrete) { ERROR_THROW("Unexpected PcvClockRatesListType: %u (module = %s)", type, Board::GetModuleName(module, false)); } *outCount = tmpOutCount; } void Board::ResetToStock() { Result rc = 0; if(hosversionAtLeast(9,0,0)) { std::uint32_t confId = 0; rc = apmExtGetCurrentPerformanceConfiguration(&confId); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); SysClkApmConfiguration* apmConfiguration = NULL; for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) { if(sysclk_g_apm_configurations[i].id == confId) { apmConfiguration = &sysclk_g_apm_configurations[i]; break; } } if(!apmConfiguration) { ERROR_THROW("Unknown apm configuration: %x", confId); } Board::SetHz(SysClkModule_CPU, apmConfiguration->cpu_hz); Board::SetHz(SysClkModule_GPU, apmConfiguration->gpu_hz); Board::SetHz(SysClkModule_MEM, apmConfiguration->mem_hz); } else { std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); rc = apmExtSysRequestPerformanceMode(mode); ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); } } void Board::ResetToStockCpu() { Result rc = 0; if(hosversionAtLeast(9,0,0)) { std::uint32_t confId = 0; rc = apmExtGetCurrentPerformanceConfiguration(&confId); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); SysClkApmConfiguration* apmConfiguration = NULL; for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) { if(sysclk_g_apm_configurations[i].id == confId) { apmConfiguration = &sysclk_g_apm_configurations[i]; break; } } if(!apmConfiguration) { ERROR_THROW("Unknown apm configuration: %x", confId); } Board::SetHz(SysClkModule_CPU, apmConfiguration->cpu_hz); } else { std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); rc = apmExtSysRequestPerformanceMode(mode); ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); } } void Board::ResetToStockMem() { Result rc = 0; if(hosversionAtLeast(9,0,0)) { std::uint32_t confId = 0; rc = apmExtGetCurrentPerformanceConfiguration(&confId); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); SysClkApmConfiguration* apmConfiguration = NULL; for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) { if(sysclk_g_apm_configurations[i].id == confId) { apmConfiguration = &sysclk_g_apm_configurations[i]; break; } } if(!apmConfiguration) { ERROR_THROW("Unknown apm configuration: %x", confId); } Board::SetHz(SysClkModule_MEM, apmConfiguration->mem_hz); } else { std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); rc = apmExtSysRequestPerformanceMode(mode); ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); } } void Board::ResetToStockGpu() { Result rc = 0; if(hosversionAtLeast(9,0,0)) { std::uint32_t confId = 0; rc = apmExtGetCurrentPerformanceConfiguration(&confId); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); SysClkApmConfiguration* apmConfiguration = NULL; for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) { if(sysclk_g_apm_configurations[i].id == confId) { apmConfiguration = &sysclk_g_apm_configurations[i]; break; } } if(!apmConfiguration) { ERROR_THROW("Unknown apm configuration: %x", confId); } Board::SetHz(SysClkModule_GPU, apmConfiguration->gpu_hz); } else { std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); rc = apmExtSysRequestPerformanceMode(mode); ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); } } void Board::ResetToStockDisplay() { if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) { DisplayRefresh_SetRate(60); } } u8 Board::GetHighestDockedDisplayRate() { if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) { return DisplayRefresh_GetDockedHighestAllowed(); } else return 60; } std::uint32_t Board::GetTemperatureMilli(SysClkThermalSensor sensor) { std::int32_t millis = 0; if(sensor == SysClkThermalSensor_SOC) { millis = tmp451TempSoc(); } else if(sensor == SysClkThermalSensor_PCB) { millis = tmp451TempPcb(); } else if(sensor == SysClkThermalSensor_Skin) { if(HOSSVC_HAS_TC) { Result rc; rc = tcGetSkinTemperatureMilliC(&millis); ASSERT_RESULT_OK(rc, "tcGetSkinTemperatureMilliC"); } } else if (sensor == HorizonOCThermalSensor_Battery) { batteryInfoGetChargeInfo(&info); millis = batteryInfoGetTemperatureMiliCelsius(&info); } else if (sensor == HorizonOCThermalSensor_PMIC) { millis = 50000; } else { ASSERT_ENUM_VALID(SysClkThermalSensor, sensor); } return std::max(0, millis); } std::int32_t Board::GetPowerMw(SysClkPowerSensor sensor) { switch(sensor) { case SysClkPowerSensor_Now: return max17050PowerNow(); case SysClkPowerSensor_Avg: return max17050PowerAvg(); default: ASSERT_ENUM_VALID(SysClkPowerSensor, sensor); } return 0; } u32 GetMaxCpuLoad() { float cpuUsage0 = std::clamp(((Systemtickfrequency - idletick0) / static_cast(Systemtickfrequency)) * 1000.0, 0.0, 1000.0); float cpuUsage1 = std::clamp(((Systemtickfrequency - idletick1) / static_cast(Systemtickfrequency)) * 1000.0, 0.0, 1000.0); float cpuUsage2 = std::clamp(((Systemtickfrequency - idletick2) / static_cast(Systemtickfrequency)) * 1000.0, 0.0, 1000.0); // float cpuUsage3 = std::clamp(((Systemtickfrequency - idletick3) / static_cast(Systemtickfrequency)) * 1000.0, 0.0, 1000.0); return std::round(std::max({cpuUsage0, cpuUsage1, cpuUsage2})); } std::uint32_t Board::GetPartLoad(SysClkPartLoad loadSource) { switch(loadSource) { case SysClkPartLoad_EMC: return t210EmcLoadAll(); case SysClkPartLoad_EMCCpu: return t210EmcLoadCpu(); case HocClkPartLoad_GPU: return GPU_Load_u; case HocClkPartLoad_CPUMax: return GetMaxCpuLoad(); case HocClkPartLoad_BAT: batteryInfoGetChargeInfo(&info); return info.RawBatteryCharge; case HocClkPartLoad_FAN: return fanLevel; default: ASSERT_ENUM_VALID(SysClkPartLoad, loadSource); } return 0; } SysClkSocType Board::GetSocType() { return g_socType; } HorizonOCConsoleType Board::GetConsoleType() { return g_consoleType; } u8 Board::GetDramID() { return g_dramID; } void Board::FetchHardwareInfos() { fuseReadSpeedos(); SetSpeedoBracket(); u64 sku = 0, dramID = 0; Result rc = splInitialize(); ASSERT_RESULT_OK(rc, "splInitialize"); rc = splGetConfig(SplConfigItem_HardwareType, &sku); ASSERT_RESULT_OK(rc, "splGetConfig"); rc = splGetConfig(SplConfigItem_DramId, &dramID); ASSERT_RESULT_OK(rc, "splGetConfig"); splExit(); switch(sku) { case 2: case 3: case 4: case 5: g_socType = SysClkSocType_Mariko; break; default: g_socType = SysClkSocType_Erista; } if (g_socType == SysClkSocType_Mariko) { CacheDvfsTable(); } g_consoleType = (HorizonOCConsoleType)sku; g_dramID = (u8)dramID; } /* * Switch Power domains (max77620): * Name | Usage | uV step | uV min | uV default | uV max | Init *-------+---------------+---------+--------+------------+---------+------------------ * sd0 | SoC | 12500 | 600000 | 625000 | 1400000 | 1.125V (pkg1.1) * sd1 | SDRAM | 12500 | 600000 | 1125000 | 1125000 | 1.1V (pkg1.1) * sd2 | ldo{0-1, 7-8} | 12500 | 600000 | 1325000 | 1350000 | 1.325V (pcv) * sd3 | 1.8V general | 12500 | 600000 | 1800000 | 1800000 | * ldo0 | Display Panel | 25000 | 800000 | 1200000 | 1200000 | 1.2V (pkg1.1) * ldo1 | XUSB, PCIE | 25000 | 800000 | 1050000 | 1050000 | 1.05V (pcv) * ldo2 | SDMMC1 | 50000 | 800000 | 1800000 | 3300000 | * ldo3 | GC ASIC | 50000 | 800000 | 3100000 | 3100000 | 3.1V (pcv) * ldo4 | RTC | 12500 | 800000 | 850000 | 850000 | 0.85V (AO, pcv) * ldo5 | GC Card | 50000 | 800000 | 1800000 | 1800000 | 1.8V (pcv) * ldo6 | Touch, ALS | 50000 | 800000 | 2900000 | 2900000 | 2.9V (pcv) * ldo7 | XUSB | 50000 | 800000 | 1050000 | 1050000 | 1.05V (pcv) * ldo8 | XUSB, DP, MCU | 50000 | 800000 | 1050000 | 2800000 | 1.05V/2.8V (pcv) typedef enum { PcvPowerDomainId_Max77620_Sd0 = 0x3A000080, PcvPowerDomainId_Max77620_Sd1 = 0x3A000081, // vdd2 PcvPowerDomainId_Max77620_Sd2 = 0x3A000082, PcvPowerDomainId_Max77620_Sd3 = 0x3A000083, PcvPowerDomainId_Max77620_Ldo0 = 0x3A0000A0, PcvPowerDomainId_Max77620_Ldo1 = 0x3A0000A1, PcvPowerDomainId_Max77620_Ldo2 = 0x3A0000A2, PcvPowerDomainId_Max77620_Ldo3 = 0x3A0000A3, PcvPowerDomainId_Max77620_Ldo4 = 0x3A0000A4, PcvPowerDomainId_Max77620_Ldo5 = 0x3A0000A5, PcvPowerDomainId_Max77620_Ldo6 = 0x3A0000A6, PcvPowerDomainId_Max77620_Ldo7 = 0x3A0000A7, PcvPowerDomainId_Max77620_Ldo8 = 0x3A0000A8, PcvPowerDomainId_Max77621_Cpu = 0x3A000003, PcvPowerDomainId_Max77621_Gpu = 0x3A000004, PcvPowerDomainId_Max77812_Cpu = 0x3A000003, PcvPowerDomainId_Max77812_Gpu = 0x3A000004, PcvPowerDomainId_Max77812_Dram = 0x3A000005, // vddq } PowerDomainId; */ std::uint32_t Board::GetVoltage(HocClkVoltage voltage) { RgltrSession session; Result rc = 0; u32 out = 0; switch(voltage) { case HocClkVoltage_SOC: rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77620_Sd0); ASSERT_RESULT_OK(rc, "rgltrOpenSession") rgltrGetVoltage(&session, &out); rgltrCloseSession(&session); break; case HocClkVoltage_EMCVDD2: rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77620_Sd1); ASSERT_RESULT_OK(rc, "rgltrOpenSession") rgltrGetVoltage(&session, &out); rgltrCloseSession(&session); break; case HocClkVoltage_CPU: if(Board::GetSocType() == SysClkSocType_Mariko) rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77621_Cpu); else rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77812_Cpu); ASSERT_RESULT_OK(rc, "rgltrOpenSession") rgltrGetVoltage(&session, &out); rgltrCloseSession(&session); break; case HocClkVoltage_GPU: if(Board::GetSocType() == SysClkSocType_Mariko) rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77621_Gpu); else rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77812_Gpu); ASSERT_RESULT_OK(rc, "rgltrOpenSession") rgltrGetVoltage(&session, &out); rgltrCloseSession(&session); break; case HocClkVoltage_EMCVDDQ_MarikoOnly: if(Board::GetSocType() == SysClkSocType_Mariko) { rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77812_Dram); ASSERT_RESULT_OK(rc, "rgltrOpenSession") rgltrGetVoltage(&session, &out); rgltrCloseSession(&session); } else { out = Board::GetVoltage(HocClkVoltage_EMCVDD2); } break; case HocClkVoltage_Display: rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77620_Ldo0); ASSERT_RESULT_OK(rc, "rgltrOpenSession") rgltrGetVoltage(&session, &out); rgltrCloseSession(&session); break; case HocClkVoltage_Battery: batteryInfoGetChargeInfo(&info); out = info.VoltageAvg; break; default: ASSERT_ENUM_VALID(HocClkVoltage, voltage); } return out > 0 ? out : 0; } void Board::SetSpeedoBracket() { if (cpuSpeedo2 >= 1754) { speedoBracket = 3; } else if (cpuSpeedo2 >= 1690) { speedoBracket = 2; } else if (cpuSpeedo2 > 1625) { speedoBracket = 1; } else { speedoBracket = 0; } } u32 Board::GetMinimumGpuVoltage(u32 freqMhz) { if (freqMhz <= 1600) return 0; for (u32 voltageIndex = 0; voltageIndex < 22; ++voltageIndex) { if (freqMhz <= ramBrackets[speedoBracket][voltageIndex]) { return gpuDvfsArray[voltageIndex]; } } return 800; } Handle Board::GetPcvHandle() { constexpr u64 PcvID = 0x10000000000001a; u64 processIDList[80]{}; s32 processCount = 0; Handle handle = INVALID_HANDLE; DebugEventInfo debugEvent{}; /* Get all running processes. */ Result resultGetProcessList = svcGetProcessList(&processCount, processIDList, std::size(processIDList)); if (R_FAILED(resultGetProcessList)) { return INVALID_HANDLE; } /* Try to find pcv. */ for (int i = 0; i < processCount; ++i) { if (handle != INVALID_HANDLE) { svcCloseHandle(handle); handle = INVALID_HANDLE; } /* Try to debug process, if it fails, try next process. */ Result resultSvcDebugProcess = svcDebugActiveProcess(&handle, processIDList[i]); if (R_FAILED(resultSvcDebugProcess)) { continue; } /* Try to get a debug event. */ Result resultDebugEvent = svcGetDebugEvent(&debugEvent, handle); if (R_SUCCEEDED(resultDebugEvent)) { if (debugEvent.info.create_process.program_id == PcvID) { return handle; } } } /* Failed to get handle. */ return INVALID_HANDLE; } void Board::CacheDvfsTable() { const u32 voltagePattern[] = { 600000, 12500, 1400000, }; Handle handle = GetPcvHandle(); if (handle == INVALID_HANDLE) { FileUtils::LogLine("[Board] Invalid handle!"); return; } MemoryInfo memoryInfo = {}; u64 address = 0; u32 pageInfo = 0; constexpr u32 PageSize = 0x1000; u8 buffer[PageSize]; /* Loop until failure. */ while (true) { /* Find pcv heap. */ while (true) { Result resultProcessMemory = svcQueryDebugProcessMemory(&memoryInfo, &pageInfo, handle, address); address = memoryInfo.addr + memoryInfo.size; if (R_FAILED(resultProcessMemory) || !address) { svcCloseHandle(handle); FileUtils::LogLine("[Board] Failed to get process data. %u", R_DESCRIPTION(resultProcessMemory)); handle = INVALID_HANDLE; return; } if (memoryInfo.size && (memoryInfo.perm & 3) == 3 && static_cast(memoryInfo.type) == 0x04) { /* Found valid memory. */ break; } } for (u64 base = 0; base < memoryInfo.size; base += PageSize) { u32 memorySize = std::min(memoryInfo.size, static_cast(PageSize)); if (R_FAILED(svcReadDebugProcessMemory(buffer, handle, base + memoryInfo.addr, memorySize))) { break; } u8 *resultPattern = static_cast(memmem_impl(buffer, sizeof(buffer), voltagePattern, sizeof(voltagePattern))); u32 index = resultPattern - buffer; if (!resultPattern) { continue; } /* Assuming mariko. */ const u32 vmax = 800; constexpr u32 DvfsTableOffset = 312; if (!std::memcmp(&buffer[index + DvfsTableOffset], &vmax, sizeof(vmax))) { std::memcpy(dvfsTable, &buffer[index + DvfsTableOffset], sizeof(dvfsTable)); dvfsAddress = base + memoryInfo.addr + DvfsTableOffset + index; } svcCloseHandle(handle); handle = INVALID_HANDLE; return; } } svcCloseHandle(handle); handle = INVALID_HANDLE; return; } void Board::PcvHijackDvfs(u32 vmin) { u32 table[192]; static_assert(sizeof(table) == sizeof(dvfsTable)); std::memcpy(table, dvfsTable, sizeof(dvfsTable)); if (ramVmin == vmin) { return; } for (u32 i = 0; i < std::size(table); ++i) { if (table[i] && table[i] <= vmin) { table[i] = vmin; } } Handle handle = GetPcvHandle(); if (handle == INVALID_HANDLE) { FileUtils::LogLine("Invalid handle!"); return; } Result rc = svcWriteDebugProcessMemory(handle, table, dvfsAddress, sizeof(table)); if (R_SUCCEEDED(rc)) { ramVmin = vmin; } svcCloseHandle(handle); FileUtils::LogLine("[dvfs] voltage set to %u mV", vmin); } bool Board::IsDram8GB() { SecmonArgs args = {}; args.X[0] = 0xF0000002; args.X[1] = MC_REGISTER_BASE + MC_EMEM_CFG_0; svcCallSecureMonitor(&args); if(args.X[1] == (MC_REGISTER_BASE + MC_EMEM_CFG_0)) { // if param 1 is identical read failed writeNotification("Horizon OC\nSecmon read failed!\n This may be a hardware issue!"); return false; } else return args.X[1] == 0x00002000 ? true : false; } void Board::SetGpuSchedulingMode(GpuSchedulingMode mode, GpuSchedulingOverrideMethod method) { if (nvCheck_sched == 1 && method == GpuSchedulingOverrideMethod_NvService) { return; } u32 temp; bool enabled = false; switch(mode) { case GpuSchedulingMode_DoNotOverride: if (method == GpuSchedulingOverrideMethod_Ini) { const char* ini_path = "sdmc:/atmosphere/config/system_settings.ini"; const char* section = "am.gpu"; const char* key = "gpu_scheduling_enabled"; // Remove the key from the INI ini_puts(section, key, NULL, ini_path); } return; case GpuSchedulingMode_Disabled: if(method == GpuSchedulingOverrideMethod_NvService) nvIoctl(fd2, NVSCHED_CTRL_DISABLE, &temp); else enabled = false; break; case GpuSchedulingMode_Enabled: if(method == GpuSchedulingOverrideMethod_NvService) nvIoctl(fd2, NVSCHED_CTRL_ENABLE, &temp); else enabled = true; break; default: ASSERT_ENUM_VALID(GpuSchedulingMode, mode); } if(method == GpuSchedulingOverrideMethod_Ini) { const char* ini_path = "sdmc:/atmosphere/config/system_settings.ini"; const char* section = "am.gpu"; const char* key = "gpu_scheduling_enabled"; const char* value = enabled ? "u8!0x1" : "u8!0x0"; ini_puts(section, key, value, ini_path); } } void Board::SetDisplayRefreshDockedState(bool docked) { if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) { DisplayRefresh_SetDockedState(docked); } } typedef struct EristaCpuUvEntry { u32 tune0; u32 tune1; } EristaCpuUvEntry; typedef struct MarikoCpuUvEntry { u32 tune0_low; u32 tune0_high; u32 tune1_low; u32 tune1_high; } MarikoCpuUvEntry; EristaCpuUvEntry eristaCpuUvTable[5] = { {0xffff, 0x27007ff}, {0xefff, 0x27407ff}, {0xdfff, 0x27807ff}, {0xdfdf, 0x27a07ff}, {0xcfdf, 0x37007ff}, }; MarikoCpuUvEntry marikoCpuUvLow[12] = { {0xffa0, 0xffff, 0x21107ff, 0}, {0x0, 0xffdf, 0x21107ff, 0x27207ff}, {0xffdf, 0xffdf, 0x21107ff, 0x27307ff}, {0xffff, 0xffdf, 0x21107ff, 0x27407ff}, {0x0, 0xffdf, 0x21607ff, 0x27707ff}, {0x0, 0xffdf, 0x21607ff, 0x27807ff}, {0x0, 0xdfff, 0x21607ff, 0x27b07ff}, {0xdfff, 0xdfff, 0x21707ff, 0x27b07ff}, {0xdfff, 0xdfff, 0x21707ff, 0x27c07ff}, {0xdfff, 0xdfff, 0x21707ff, 0x27d07ff}, {0xdfff, 0xdfff, 0x21707ff, 0x27e07ff}, {0xdfff, 0xdfff, 0x21707ff, 0x27f07ff}, }; MarikoCpuUvEntry marikoCpuUvHigh[12] = { {0x0, 0xffff, 0, 0}, {0x0, 0xffdf, 0, 0x27207ff}, {0x0, 0xffdf, 0, 0x27307ff}, {0x0, 0xffdf, 0, 0x27407ff}, {0x0, 0xffdf, 0, 0x27707ff}, {0x0, 0xffdf, 0, 0x27807ff}, {0x0, 0xdfff, 0, 0x27b07ff}, {0x0, 0xdfff, 0, 0x27c07ff}, {0x0, 0xdfff, 0, 0x27d07ff}, {0x0, 0xdfff, 0, 0x27e07ff}, {0x0, 0xdfff, 0, 0x27f07ff}, {0x0, 0xdfff, 0, 0x27f07ff}, }; void Board::SetCpuUvLevel(u32 levelLow, u32 levelHigh, u32 tbreakPoint) { u32* tune0_ptr = (u32*)(cldvfs + CL_DVFS_TUNE0_0); u32* tune1_ptr = (u32*)(cldvfs + CL_DVFS_TUNE1_0); if(Board::GetSocType() == SysClkSocType_Mariko) { if(Board::GetHz(SysClkModule_CPU) < tbreakPoint && (levelLow || levelHigh)) { if(levelLow) { *tune0_ptr = marikoCpuUvLow[levelLow-1].tune0_low; *tune1_ptr = marikoCpuUvLow[levelLow-1].tune1_low; } return; } else { if(levelLow) { *tune0_ptr = marikoCpuUvLow[levelLow-1].tune0_low; *tune1_ptr = marikoCpuUvLow[levelLow-1].tune1_low; } if(levelHigh) { *tune0_ptr = marikoCpuUvHigh[levelHigh-1].tune0_high; *tune1_ptr = marikoCpuUvHigh[levelHigh-1].tune1_high; } return; } if(Board::GetHz(SysClkModule_CPU) < tbreakPoint || (!levelLow)) { // account for tbreak *tune0_ptr = 0xCFFF; *tune1_ptr = 0xFF072201; return; } else if (Board::GetHz(SysClkModule_CPU) >= tbreakPoint || (!levelHigh)) { *tune0_ptr = cachedMarikoUvHighTune0; // per console? *tune1_ptr = 0xFFF7FF3F; return; } } else { if(Board::GetHz(SysClkModule_CPU) < tbreakPoint || (!levelLow)) { // account for tbreak *tune0_ptr = cachedEristaUvLowTune0; // I think each erista has a different tune0/tune1? *tune1_ptr = cachedEristaUvLowTune1; return; } else { if(levelLow) { *tune0_ptr = eristaCpuUvTable[levelLow-1].tune0; *tune1_ptr = eristaCpuUvTable[levelLow-1].tune1; } else { *tune0_ptr = 0x0; *tune1_ptr = 0x0; } } } } /* enum TableConfig: u32 { DEFAULT_TABLE = 1, TBREAK_1581 = 2, TBREAK_1683 = 3, EXTREME_TABLE = 4, }; */ u32 Board::CalculateTbreak(u32 table) { if(Board::GetSocType() == SysClkSocType_Erista) return 1581000000; else { switch(table) { case 1 ... 2: case 4: return 1581000000; case 3: return 1683000000; default: return 1581000000; } } }