sysmodule/overlay: re-add part load, voltage readings and animated logo

This commit is contained in:
souldbminersmwc
2025-12-03 19:51:00 -05:00
parent c84bb9d7e6
commit 292ad19d2d
15 changed files with 620 additions and 136 deletions

View File

@@ -28,13 +28,39 @@
#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);
@@ -85,6 +111,65 @@ PcvModuleId Board::GetPcvModuleId(SysClkModule sysclkModule)
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;
@@ -118,6 +203,24 @@ void Board::Initialize()
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();
}
@@ -142,6 +245,13 @@ void Board::Exit()
max17050Exit();
tmp451Exit();
threadClose(&gpuLThread);
threadClose(&cpuCore0Thread);
threadClose(&cpuCore1Thread);
threadClose(&cpuCore2Thread);
threadClose(&cpuCore3Thread);
}
SysClkProfile Board::GetProfile()
@@ -464,21 +574,26 @@ std::int32_t Board::GetPowerMw(SysClkPowerSensor sensor)
return 0;
}
std::uint32_t Board::GetRamLoad(SysClkRamLoad loadSource)
{
std::uint32_t Board::GetPartLoad(SysClkPartLoad loadSource)
{
switch(loadSource)
{
case SysClkRamLoad_All:
case SysClkPartLoad_EMC:
return t210EmcLoadAll();
case SysClkRamLoad_Cpu:
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(SysClkRamLoad, loadSource);
ASSERT_ENUM_VALID(SysClkPartLoad, loadSource);
}
return 0;
}
SysClkSocType Board::GetSocType() {
return g_socType;
}
@@ -500,12 +615,108 @@ void Board::FetchHardwareInfos()
switch(sku)
{
case 2 .. 5:
case 2:
case 3:
case 4:
case 5:
g_socType = SysClkSocType_Mariko;
break;
default:
g_socType = SysClkSocType_Erista;
}
g_consoleType = sku;
}
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;
}

View File

@@ -51,9 +51,10 @@ class Board
static void GetFreqList(SysClkModule module, std::uint32_t* outList, std::uint32_t maxCount, std::uint32_t* outCount);
static std::uint32_t GetTemperatureMilli(SysClkThermalSensor sensor);
static std::int32_t GetPowerMw(SysClkPowerSensor sensor);
static std::uint32_t GetRamLoad(SysClkRamLoad load);
static std::uint32_t GetPartLoad(SysClkPartLoad load);
static SysClkSocType GetSocType();
static HorizonOCConsoleType GetConsoleType();
static std::uint32_t GetVoltage(HocClkVoltage voltage);
protected:
static void FetchHardwareInfos();

View File

@@ -227,6 +227,8 @@ void ClockManager::RefreshFreqTableRow(SysClkModule module)
void ClockManager::Tick()
{
std::scoped_lock lock{this->contextMutex};
AppletOperationMode opMode = appletGetOperationMode();
if(this->config->GetConfigValue(HocClkConfigValue_HandheldTDP) && opMode == AppletOperationMode_Handheld) {
if(Board::GetConsoleType() == HorizonOCConsoleType_Lite) {
if(Board::GetPowerMw(SysClkPowerSensor_Now) < -(int)this->config->GetConfigValue(HocClkConfigValue_LiteTDPLimit)) {
@@ -254,7 +256,6 @@ void ClockManager::Tick()
std::uint32_t nearestHz = 0;
std::uint32_t mode = 0;
AppletOperationMode opMode = appletGetOperationMode();
Result rc = apmExtGetCurrentPerformanceConfiguration(&mode);
ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration");
@@ -427,9 +428,14 @@ bool ClockManager::RefreshContext()
}
// ram load do not and should not force a refresh, hasChanged untouched
for (unsigned int loadSource = 0; loadSource < SysClkRamLoad_EnumMax; loadSource++)
for (unsigned int loadSource = 0; loadSource < SysClkPartLoad_EnumMax; loadSource++)
{
this->context->ramLoad[loadSource] = Board::GetRamLoad((SysClkRamLoad)loadSource);
this->context->PartLoad[loadSource] = Board::GetPartLoad((SysClkPartLoad)loadSource);
}
for (unsigned int voltageSource = 0; voltageSource < HocClkVoltage_EnumMax; voltageSource++)
{
this->context->voltages[voltageSource] = Board::GetVoltage((HocClkVoltage)voltageSource);
}
if (this->ConfigIntervalTimeout(SysClkConfigValue_CsvWriteIntervalMs, ns, &this->lastCsvWriteNs))

View File

@@ -47,9 +47,11 @@ extern "C"
std::uint32_t __nx_applet_type = AppletType_None;
TimeServiceType __nx_time_service_type = TimeServiceType_System;
std::uint32_t __nx_fs_num_sessions = 1;
u32 __nx_nv_transfermem_size = 0x8000;
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
NvServiceType __nx_nv_service_type = NvServiceType_Factory;
void __libnx_initheap(void)
{

View File

@@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <ctime>
#include <cstdio>
static void writeNotification(const std::string& message) {
const char* flagPath = "sdmc:/config/ultrahand/flags/NOTIFICATIONS.flag";
FILE* flagFile = fopen(flagPath, "r");
if (!flagFile) {
return;
}
fclose(flagFile);
std::string filename = "Horizon OC -" + std::to_string(std::time(nullptr)) + ".notify";
std::string fullPath = "sdmc:/config/ultrahand/notifications/" + filename;
FILE* file = fopen(fullPath.c_str(), "w");
if (file) {
fprintf(file, "{\n");
fprintf(file, " \"text\": \"%s\",\n", message.c_str());
fprintf(file, " \"fontSize\": 28\n");
fprintf(file, "}\n");
fclose(file);
}
}

View File

@@ -0,0 +1,95 @@
#pragma once
/*
* SDx actual min is 625 mV. Multipliers 0/1 reserved.
* SD0 max is 1400 mV
* SD1 max is 1550 mV
* SD2 max is 3787.5 mV
* SD3 max is 3787.5 mV
*/
/*
* 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)
*/
// GPIOs T210: 3: 3.3V, 5: CPU PMIC, 6: GPU PMIC, 7: DSI/VI 1.2V powered by ldo0.
/*
* OTP: T210 - T210B01:
* SD0: 1.0V 1.05V - SoC. EN Based on FPSSRC.
* SD1: 1.15V 1.1V - DRAM for T210. EN Based on FPSSRC.
* SD2: 1.35V 1.35V
* SD3: 1.8V 1.8V
* All powered off?
* LDO0: -- -- - Display
* LDO1: 1.05V 1.05V
* LDO2: -- -- - SD
* LDO3: 3.1V 3.1V - GC ASIC
* LDO4: 1.0V 0.8V - Needed for RTC domain on T210.
* LDO5: 3.1V 3.1V
* LDO6: 2.8V 2.9V - Touch.
* LDO7: 1.05V 1.0V
* LDO8: 1.05V 1.0V
*/
/*
* MAX77620_AME_GPIO: control GPIO modes (bits 0 - 7 correspond to GPIO0 - GPIO7); 0 -> GPIO, 1 -> alt-mode
* MAX77620_REG_GPIOx: 0x9 sets output and enable
*/
typedef enum {
PcvPowerDomain_Max77620_Sd0 = 0,
PcvPowerDomain_Max77620_Sd1 = 1,
PcvPowerDomain_Max77620_Sd2 = 2,
PcvPowerDomain_Max77620_Sd3 = 3,
PcvPowerDomain_Max77620_Ldo0 = 4,
PcvPowerDomain_Max77620_Ldo1 = 5,
PcvPowerDomain_Max77620_Ldo2 = 6,
PcvPowerDomain_Max77620_Ldo3 = 7,
PcvPowerDomain_Max77620_Ldo4 = 8,
PcvPowerDomain_Max77620_Ldo5 = 9,
PcvPowerDomain_Max77620_Ldo6 = 10,
PcvPowerDomain_Max77620_Ldo7 = 11,
PcvPowerDomain_Max77620_Ldo8 = 12,
PcvPowerDomain_Max77621_Cpu = 13,
PcvPowerDomain_Max77621_Gpu = 14,
PcvPowerDomain_Max77812_Cpu = 15,
PcvPowerDomain_Max77812_Gpu = 16,
PcvPowerDomain_Max77812_Dram = 17,
} PowerDomain;
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;

View File

@@ -0,0 +1,19 @@
#pragma once
#include <switch.h>
#include "pcv_types.h"
typedef struct {
Service s;
} RgltrSession;
Result rgltrInitialize(void);
void rgltrExit(void);
Service* rgltrGetServiceSession(void);
Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id);
void rgltrCloseSession(RgltrSession* session);
Result rgltrGetVoltage(RgltrSession* session, u32 *out_volt);
Result rgltrGetPowerModuleNumLimit(u32 *out);
Result rgltrGetVoltageEnabled(RgltrSession* session, u32 *out);

View File

@@ -0,0 +1,41 @@
#include <switch.h>
#include "rgltr.h"
#include "rgltr_services.h" // for extern Service g_rgltrSrv, etc.
// Global service handle
Service g_rgltrSrv;
Result rgltrInitialize(void) {
if (hosversionBefore(8, 0, 0)) {
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
}
return smGetService(&g_rgltrSrv, "rgltr");
}
void rgltrExit(void) {
serviceClose(&g_rgltrSrv);
}
Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id) {
const u32 in = (u32)module_id;
return serviceDispatchIn(
&g_rgltrSrv,
0,
in,
.out_num_objects = 1,
.out_objects = &session_out->s
);
}
Result rgltrGetVoltage(RgltrSession* session, u32* out_volt) {
u32 temp = 0;
Result rc = serviceDispatchOut(&session->s, 4, temp);
if (R_SUCCEEDED(rc)) {
*out_volt = temp;
}
return rc;
}
void rgltrCloseSession(RgltrSession* session) {
serviceClose(&session->s);
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include <switch.h> // for Service, Result, hosversionBefore(), smGetService(), serviceClose(), etc.
#include "rgltr.h" // for RgltrSession, PowerDomainId, etc.
extern Service g_rgltrSrv;
Result rgltrInitialize(void);
void rgltrExit(void);
Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id);
Result rgltrGetVoltage(RgltrSession* session, u32* out_volt);
void rgltrCloseSession(RgltrSession* session);