Files
Horizon-OC/Source/sys-clk/sysmodule/src/board.cpp

723 lines
20 KiB
C++

/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
/* --------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net>
* 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 <nxExt.h>
#include "board.h"
#include "errors.h"
#include "rgltr.h"
#include "file_utils.h"
#include <algorithm> // for std::clamp
#include <math.h>
#include <numeric>
#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;
}