/* * 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 . * */ /* -------------------------------------------------------------------------- * "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 * -------------------------------------------------------------------------- */ #include #include "board.h" #include "errors.h" #include "rgltr.h" #include "file_utils.h" #include // for std::clamp #include #include #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 systemtickfrequency 19200000 #define systemtickfrequencyF 19200000.0f #define CPU_TICK_WAIT (1000'000ULL) Result nvCheck = 1; Thread gpuLThread; Thread cpuCore0Thread; Thread cpuCore1Thread; Thread cpuCore2Thread; Thread cpuCore3Thread; uint32_t GPU_Load_u = 0, fd = 0; static SysClkSocType g_socType = SysClkSocType_Erista; static HorizonOCConsoleType g_consoleType = HorizonOCConsoleType_Unknown; uint64_t idletick0 = systemtickfrequency; uint64_t idletick1 = systemtickfrequency; uint64_t idletick2 = systemtickfrequency; uint64_t idletick3 = systemtickfrequency; u32 cpu0, cpu1, cpu2, cpu3, cpuAvg; 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 CheckCore0(void*) { while(true) { uint64_t idletick_a0 = 0; uint64_t idletick_b0 = 0; svcGetInfo(&idletick_b0, InfoType_IdleTickCount, INVALID_HANDLE, 0); svcSleepThread(CPU_TICK_WAIT); svcGetInfo(&idletick_a0, InfoType_IdleTickCount, INVALID_HANDLE, 0); idletick0 = idletick_a0 - idletick_b0; } } void CheckCore1(void*) { while(true) { uint64_t idletick_a1 = 0; uint64_t idletick_b1 = 0; svcGetInfo(&idletick_b1, InfoType_IdleTickCount, INVALID_HANDLE, 1); svcSleepThread(CPU_TICK_WAIT); svcGetInfo(&idletick_a1, InfoType_IdleTickCount, INVALID_HANDLE, 1); idletick1 = idletick_a1 - idletick_b1; } } void CheckCore2(void*) { while(true) { uint64_t idletick_a2 = 0; uint64_t idletick_b2 = 0; svcGetInfo(&idletick_b2, InfoType_IdleTickCount, INVALID_HANDLE, 2); svcSleepThread(CPU_TICK_WAIT); svcGetInfo(&idletick_a2, InfoType_IdleTickCount, INVALID_HANDLE, 2); idletick2 = idletick_a2 - idletick_b2; } } void CheckCore3(void*) { while(true) { uint64_t idletick_a3 = 0; uint64_t idletick_b3 = 0; svcGetInfo(&idletick_b3, InfoType_IdleTickCount, INVALID_HANDLE, 3); svcSleepThread(CPU_TICK_WAIT); svcGetInfo(&idletick_a3, InfoType_IdleTickCount, INVALID_HANDLE, 3); idletick3 = idletick_a3 - idletick_b3; } } 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 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"); if (R_SUCCEEDED(nvInitialize())) nvCheck = nvOpen(&fd, "/dev/nvhost-ctrl-gpu"); threadCreate(&gpuLThread, gpuLoadThread, NULL, NULL, 0x1000, 0x3F, -2); threadStart(&gpuLThread); threadCreate(&cpuCore0Thread, CheckCore0, NULL, NULL, 0x1000, 0x10, 0); threadStart(&cpuCore0Thread); threadCreate(&cpuCore1Thread, CheckCore1, NULL, NULL, 0x1000, 0x10, 1); threadStart(&cpuCore1Thread); threadCreate(&cpuCore2Thread, CheckCore2, NULL, NULL, 0x1000, 0x10, 2); threadStart(&cpuCore2Thread); threadCreate(&cpuCore3Thread, CheckCore3, NULL, NULL, 0x1000, 0x10, 3); threadStart(&cpuCore3Thread); FetchHardwareInfos(); } 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); } 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(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"); clkrstCloseSession(&session); } else { 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(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) { switch(module) { case SysClkModule_CPU: return t210ClkCpuFreq(); case SysClkModule_GPU: return t210ClkGpuFreq(); case SysClkModule_MEM: return t210ClkMemFreq(); 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"); } } 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 { 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; } 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_CPUAvg: return (idletick0 + idletick1 + idletick2 + idletick3) / 4; default: ASSERT_ENUM_VALID(SysClkPartLoad, loadSource); } return 0; } SysClkSocType Board::GetSocType() { return g_socType; } HorizonOCConsoleType Board::GetConsoleType() { return g_consoleType; } void Board::FetchHardwareInfos() { u64 sku = 0; Result rc = splInitialize(); ASSERT_RESULT_OK(rc, "splInitialize"); rc = splGetConfig(SplConfigItem_HardwareType, &sku); 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; } g_consoleType = (HorizonOCConsoleType)sku; } /* * 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; 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: rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77621_Cpu); ASSERT_RESULT_OK(rc, "rgltrOpenSession") rgltrGetVoltage(&session, &out); rgltrCloseSession(&session); break; case HocClkVoltage_GPU: rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77621_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); } break; case HocClkVoltage_Display: rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77620_Ldo0); ASSERT_RESULT_OK(rc, "rgltrOpenSession") rgltrGetVoltage(&session, &out); rgltrCloseSession(&session); break; default: ASSERT_ENUM_VALID(HocClkVoltage, voltage); } return out > 0 ? out : 0; }