hoc-overlay: add fan control feature to sysmodule

This commit is contained in:
souldbminersmwc
2025-10-02 16:26:50 -04:00
parent fde9a5b1a3
commit b61906cc49
14 changed files with 682 additions and 44 deletions

View File

@@ -21,6 +21,7 @@ extern "C" {
#include "sysclk/apm.h"
#include "sysclk/config.h"
#include "sysclk/errors.h"
#include "sysclk/psm_ext.h"
#ifdef __cplusplus
}

View File

@@ -22,6 +22,8 @@ typedef enum {
HocClkConfigValue_UncappedClocks,
HocClkConfigValue_OverwriteBoostMode,
HocClkConfigValue_SyncReverseNXMode,
HocClkConfigValue_DockedGovernor,
HocClkConfigValue_HandheldGovernor,
SysClkConfigValue_EnumMax,
} SysClkConfigValue;
@@ -49,6 +51,10 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr
return pretty ? "Overwrite Boost Mode" : "ow_boost";
case HocClkConfigValue_SyncReverseNXMode:
return pretty ? "ReverseNX Sync" : "rnx_sync";
case HocClkConfigValue_DockedGovernor:
return pretty ? "Docked Governor" : "governor_d";
case HocClkConfigValue_HandheldGovernor:
return pretty ? "Handheld Governor" : "governor_hh";
default:
return NULL;
}

View File

@@ -0,0 +1,77 @@
#pragma once
#include <switch.h>
typedef enum {
PsmPDC_NewPDO = 1, //Received new Power Data Object
PsmPDC_NoPD = 2, //No Power Delivery source is detected
PsmPDC_AcceptedRDO = 3 //Received and accepted Request Data Object
} PsmChargeInfoPDC; //BM92T series
typedef enum {
PsmPowerRole_Sink = 1,
PsmPowerRole_Source = 2
} PsmPowerRole;
const char* PsmPowerRoleToStr(PsmPowerRole role);
typedef enum {
PsmInfoChargerType_None = 0,
PsmInfoChargerType_PD = 1,
PsmInfoChargerType_TypeC_1500mA = 2,
PsmInfoChargerType_TypeC_3000mA = 3,
PsmInfoChargerType_DCP = 4,
PsmInfoChargerType_CDP = 5,
PsmInfoChargerType_SDP = 6,
PsmInfoChargerType_Apple_500mA = 7,
PsmInfoChargerType_Apple_1000mA = 8,
PsmInfoChargerType_Apple_2000mA = 9
} PsmInfoChargerType;
const char* PsmInfoChargerTypeToStr(PsmInfoChargerType type);
typedef enum {
PsmFlags_NoHub = BIT(0), //If hub is disconnected
PsmFlags_Rail = BIT(8), //At least one Joy-con is charging from rail
PsmFlags_SPDSRC = BIT(12), //OTG
PsmFlags_ACC = BIT(16) //Accessory
} PsmChargeInfoFlags;
typedef struct {
int32_t InputCurrentLimit; //Input (Sink) current limit in mA
int32_t VBUSCurrentLimit; //Output (Source/VBUS/OTG) current limit in mA
int32_t ChargeCurrentLimit; //Battery charging current limit in mA (512mA when Docked, 768mA when BatteryTemperature < 17.0 C)
int32_t ChargeVoltageLimit; //Battery charging voltage limit in mV (3952mV when BatteryTemperature >= 51.0 C)
int32_t unk_x10; //Possibly an emum, getting the same value as PowerRole in all tested cases
int32_t unk_x14; //Possibly flags
PsmChargeInfoPDC PDCState; //Power Delivery Controller State
int32_t BatteryTemperature; //Battery temperature in milli C
int32_t RawBatteryCharge; //Raw battery charged capacity per cent-mille (i.e. 100% = 100000 pcm)
int32_t VoltageAvg; //Voltage avg in mV (more in Notes)
int32_t BatteryAge; //Battery age (capacity full / capacity design) per cent-mille (i.e. 100% = 100000 pcm)
PsmPowerRole PowerRole;
PsmInfoChargerType ChargerType;
int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV
int32_t ChargerCurrentLimit; //Charger and external device current limit in mA
PsmChargeInfoFlags Flags; //Unknown flags
} PsmChargeInfo;
typedef enum {
Psm_EnableBatteryCharging = 2,
Psm_DisableBatteryCharging = 3,
Psm_EnableFastBatteryCharging = 10,
Psm_DisableFastBatteryCharging = 11,
Psm_GetBatteryChargeInfoFields = 17,
} IPsmServerCmd;
bool PsmIsChargerConnected(const PsmChargeInfo* info);
bool PsmIsCharging(const PsmChargeInfo* info);
typedef enum {
PsmBatteryState_Discharging,
PsmBatteryState_ChargingPaused,
PsmBatteryState_FastCharging
} PsmBatteryState;
PsmBatteryState PsmGetBatteryState(const PsmChargeInfo* info);
const char* PsmGetBatteryStateIcon(const PsmChargeInfo* info);

View File

@@ -0,0 +1,50 @@
#include <sysclk/psm_ext.h>
const char* PsmPowerRoleToStr(PsmPowerRole role) {
switch (role) {
case PsmPowerRole_Sink: return "Sink";
case PsmPowerRole_Source: return "Source";
default: return "Unknown";
}
}
const char* PsmInfoChargerTypeToStr(PsmInfoChargerType type) {
switch (type) {
case PsmInfoChargerType_None: return "None";
case PsmInfoChargerType_PD: return "USB-C PD";
case PsmInfoChargerType_TypeC_1500mA:
case PsmInfoChargerType_TypeC_3000mA: return "USB-C";
case PsmInfoChargerType_DCP: return "USB DCP";
case PsmInfoChargerType_CDP: return "USB CDP";
case PsmInfoChargerType_SDP: return "USB SDP";
case PsmInfoChargerType_Apple_500mA:
case PsmInfoChargerType_Apple_1000mA:
case PsmInfoChargerType_Apple_2000mA: return "Apple";
default: return "Unknown";
}
}
bool PsmIsChargerConnected(const PsmChargeInfo* info) {
return info->ChargerType != PsmInfoChargerType_None;
}
bool PsmIsCharging(const PsmChargeInfo* info) {
return PsmIsChargerConnected(info) && ((info->unk_x14 >> 8) & 1);
}
PsmBatteryState PsmGetBatteryState(const PsmChargeInfo* info) {
if (!PsmIsChargerConnected(info))
return PsmBatteryState_Discharging;
if (!PsmIsCharging(info))
return PsmBatteryState_ChargingPaused;
return PsmBatteryState_FastCharging;
}
const char* PsmGetBatteryStateIcon(const PsmChargeInfo* info) {
switch (PsmGetBatteryState(info)) {
case PsmBatteryState_Discharging: return "\u25c0"; // ◀
case PsmBatteryState_ChargingPaused:return "| |";
case PsmBatteryState_FastCharging: return "\u25b6"; // ▶
default: return "?";
}
}

View File

@@ -37,14 +37,14 @@ include $(DEVKITPRO)/libnx/switch_rules
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
# NACP building is skipped as well.
#---------------------------------------------------------------------------------
TARGET := sys-clk-manager
TARGET := horizon-oc-manager
BUILD := build.nx
SOURCES := src ../common/src ../common/src/client
RESOURCES := resources
DATA := data
INCLUDES := ../common/include
APP_TITLE := Horizon OC Manager
APP_AUTHOR := Souldbminer, meha, b0rd2dEAth and RetroNX Team
APP_AUTHOR := Souldbminer and RetroNX Team
ROMFS := $(BUILD)/romfs
BOREALIS_PATH := lib/borealis

View File

@@ -17,7 +17,7 @@ include $(DEVKITPRO)/libnx/switch_rules
# INCLUDES is a list of directories containing header files
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
#---------------------------------------------------------------------------------
TARGET := sys-clk-overlay
TARGET := horizon-oc-overlay
BUILD := build
OUTDIR := out
RESOURCES := res

View File

@@ -16,16 +16,16 @@
#include "errors.h"
#include "ipc_service.h"
ClockManager* ClockManager::instance = NULL;
ClockManager *ClockManager::instance = NULL;
ClockManager* ClockManager::GetInstance()
ClockManager *ClockManager::GetInstance()
{
return instance;
}
void ClockManager::Exit()
{
if(instance)
if (instance)
{
delete instance;
}
@@ -33,7 +33,7 @@ void ClockManager::Exit()
void ClockManager::Initialize()
{
if(!instance)
if (!instance)
{
instance = new ClockManager();
}
@@ -60,7 +60,6 @@ ClockManager::ClockManager()
this->lastCsvWriteNs = 0;
this->rnxSync = new ReverseNXSync;
}
ClockManager::~ClockManager()
@@ -119,13 +118,13 @@ std::uint32_t ClockManager::GetMaxAllowedHz(SysClkModule module, SysClkProfile p
}
else
{
if(module == SysClkModule_GPU)
if (module == SysClkModule_GPU)
{
if(profile < SysClkProfile_HandheldCharging)
if (profile < SysClkProfile_HandheldCharging)
{
return Board::GetSocType() == SysClkSocType_Mariko ? 614400000 : 460800000;
}
else if(profile <= SysClkProfile_HandheldChargingUSB)
else if (profile <= SysClkProfile_HandheldChargingUSB)
{
return 768000000;
}
@@ -211,31 +210,46 @@ void ClockManager::Tick()
for (unsigned int module = 0; module < SysClkModule_EnumMax; module++)
{
targetHz = this->context->overrideFreqs[module];
if (!targetHz)
{
targetHz = this->config->GetAutoClockHz(this->context->applicationId, (SysClkModule)module, this->context->profile);
}
if (targetHz)
{
maxHz = this->GetMaxAllowedHz((SysClkModule)module, this->context->profile);
nearestHz = this->GetNearestHz((SysClkModule)module, targetHz, maxHz);
if (nearestHz != this->context->freqs[module] && this->context->enabled && !apmExtIsBoostMode(this->context->perfConfId) && this->config->GetConfigValue(HocClkConfigValue_OverwriteBoostMode))
// if (!this->config->GetConfigValue(HocClkConfigValue_DockedGovernor) || !this->config->GetConfigValue(HocClkConfigValue_HandheldGovernor))
// {
if (!targetHz)
{
FileUtils::LogLine(
"[mgr] %s clock set : %u.%u MHz (target = %u.%u MHz)",
Board::GetModuleName((SysClkModule)module, true),
nearestHz / 1000000, nearestHz / 100000 - nearestHz / 1000000 * 10,
targetHz / 1000000, targetHz / 100000 - targetHz / 1000000 * 10);
Board::SetHz((SysClkModule)module, nearestHz);
this->context->freqs[module] = nearestHz;
} else {
Board::ResetToStockCpu();
Board::ResetToStockGpu();
targetHz = this->config->GetAutoClockHz(this->context->applicationId, (SysClkModule)module, this->context->profile);
}
if (targetHz)
{
maxHz = this->GetMaxAllowedHz((SysClkModule)module, this->context->profile);
nearestHz = this->GetNearestHz((SysClkModule)module, targetHz, maxHz);
if (nearestHz != this->context->freqs[module] && this->context->enabled && !apmExtIsBoostMode(this->context->perfConfId) && this->config->GetConfigValue(HocClkConfigValue_OverwriteBoostMode))
{
FileUtils::LogLine(
"[mgr] %s clock set : %u.%u MHz (target = %u.%u MHz)",
Board::GetModuleName((SysClkModule)module, true),
nearestHz / 1000000, nearestHz / 100000 - nearestHz / 1000000 * 10,
targetHz / 1000000, targetHz / 100000 - targetHz / 1000000 * 10);
Board::SetHz((SysClkModule)module, nearestHz);
this->context->freqs[module] = nearestHz;
}
else
{
Board::ResetToStockCpu();
Board::ResetToStockGpu();
}
// }
// } else {
// #define GOVERNOR_LOAD_THRESHOLD 80
// if(apmExtIsBoostMode(this->context->perfConfId)) {
// Board::ResetToStockCpu(); // GOVERNOR: Reset to stock clocks if boost mode (dont use governor when boosted)
// Board::ResetToStockGpu();
// } else {
// // Actually run the CPU governor
// if(t210EmcLoadCpu() > GOVERNOR_LOAD_THRESHOLD) {
// realHz = targetHz / 1000000
// }
// }
}
}
}
@@ -265,7 +279,6 @@ bool ClockManager::RefreshContext()
this->context->applicationId = applicationId;
hasChanged = true;
this->rnxSync->Reset(applicationId);
}
SysClkProfile profile = Board::GetProfile();
@@ -378,6 +391,7 @@ bool ClockManager::RefreshContext()
return hasChanged;
}
void ClockManager::SetRNXRTMode(ReverseNXMode mode) {
void ClockManager::SetRNXRTMode(ReverseNXMode mode)
{
this->rnxSync->SetRTMode(mode);
}

View File

@@ -39,6 +39,10 @@ class ClockManager
void Tick();
void WaitForNextTick();
void SetRNXRTMode(ReverseNXMode mode);
struct {
std::uint32_t count;
std::uint32_t list[SYSCLK_FREQ_LIST_MAX];
} freqTable[SysClkModule_EnumMax];
protected:
bool IsAssignableHz(SysClkModule module, std::uint32_t hz);
@@ -52,10 +56,6 @@ class ClockManager
std::atomic_bool running;
LockableMutex contextMutex;
struct {
std::uint32_t count;
std::uint32_t list[SYSCLK_FREQ_LIST_MAX];
} freqTable[SysClkModule_EnumMax];
Config* config;
SysClkContext* context;
std::uint64_t lastTempLogNs;

View File

@@ -0,0 +1,250 @@
#include "fancontrol.h"
#include "tmp451.h"
//Fan curve table
const TemperaturePoint defaultTable[] =
{
{ .temperature_c = 25.0, .fanLevel_f = 0.10 },
{ .temperature_c = 30.0, .fanLevel_f = 0.20 },
{ .temperature_c = 35.0, .fanLevel_f = 0.30 },
{ .temperature_c = 40.0, .fanLevel_f = 0.40 },
{ .temperature_c = 45.0, .fanLevel_f = 0.50 },
{ .temperature_c = 50.0, .fanLevel_f = 0.60 },
{ .temperature_c = 55.0, .fanLevel_f = 0.70 },
{ .temperature_c = 60.0, .fanLevel_f = 0.80 },
{ .temperature_c = 65.0, .fanLevel_f = 0.90 },
{ .temperature_c = 70.0, .fanLevel_f = 1.00 }
};
TemperaturePoint *fanControllerTable;
//Fan
Thread FanControllerThread;
bool fanControllerThreadExit = false;
//Log
char logPath[PATH_MAX];
//Power management
Event powerStateChangeEvent;
bool isPowerStateInitialized = false;
void CreateDir(char *dir)
{
char dirPath[PATH_MAX];
for(int i = 0; i < PATH_MAX; i++)
{
if(*(dir + i) == '/' && access(dirPath, F_OK) == -1)
{
mkdir(dirPath, 0777);
}
dirPath[i] = *(dir + i);
}
}
void InitLog()
{
if(access(LOG_DIR, F_OK) == -1)
CreateDir(LOG_DIR);
if(access(LOG_FILE, F_OK) != -1)
remove(LOG_FILE);
}
void WriteLog(char *buffer)
{
FILE *log = fopen(LOG_FILE, "a");
if(log != NULL)
{
fprintf(log, "%s\n", buffer);
}
fclose(log);
}
void WriteConfigFile(TemperaturePoint *table)
{
if(table == NULL)
{
table = malloc(sizeof(defaultTable));
memcpy(table, defaultTable, sizeof(defaultTable));
}
if(access(CONFIG_DIR, F_OK) == -1)
CreateDir(CONFIG_DIR);
FILE *config = fopen(CONFIG_FILE, "w");
fwrite(table, TABLE_SIZE, 1, config);
fclose(config);
}
void ReadConfigFile(TemperaturePoint **table_out)
{
InitLog();
*table_out = malloc(sizeof(defaultTable));
memcpy(*table_out, defaultTable, sizeof(defaultTable));
if(access(CONFIG_DIR, F_OK) == -1)
{
CreateDir(CONFIG_DIR);
WriteConfigFile(NULL);
}
else
{
if(access(CONFIG_FILE, F_OK) == -1)
{
WriteConfigFile(NULL);
}
else
{
FILE *config = fopen(CONFIG_FILE, "r");
fread(*table_out, TABLE_SIZE, 1, config);
fclose(config);
}
}
}
bool IsSystemAwake()
{
// Check if system is in sleep mode by checking applet state
// appletGetOperationMode returns the current operation mode
AppletOperationMode opMode = appletGetOperationMode();
// If in handheld or console mode, system is awake
// If in any other mode (like sleep), we consider it asleep
return (opMode == AppletOperationMode_Handheld || opMode == AppletOperationMode_Console);
}
void InitFanController(TemperaturePoint *table)
{
fanControllerTable = table;
if(R_FAILED(threadCreate(&FanControllerThread, FanControllerThreadFunction, NULL, NULL, 0x4000, 0x3F, -2)))
{
WriteLog("Error creating FanControllerThread");
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
}
void FanControllerThreadFunction(void*)
{
FanController fc;
float fanLevelSet_f = 0;
float temperatureC_f = 0;
u64 awakeSleepTime = 250000000ULL; // 0.25 second when awake (250ms - responsive)
u64 sleepSleepTime = 5000000000ULL; // 5 seconds when in sleep
int sleepCheckCounter = 0;
Result rs = fanOpenController(&fc, 0x3D000001);
if(R_FAILED(rs))
{
WriteLog("Error opening fanController");
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
while(!fanControllerThreadExit)
{
// Check if system is awake every 20 iterations (~5 seconds) to reduce overhead
sleepCheckCounter++;
if(sleepCheckCounter >= 20)
{
bool isAwake = IsSystemAwake();
sleepCheckCounter = 0;
// If system is asleep, use longer sleep interval
if(!isAwake)
{
svcSleepThread(sleepSleepTime);
continue;
}
}
rs = Tmp451GetSocTemp(&temperatureC_f);
if(R_FAILED(rs))
{
WriteLog("tsSessionGetTemperature error");
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
if(temperatureC_f >= 0 && temperatureC_f <= fanControllerTable->temperature_c)
{
float m = 0;
float q = 0;
m = fanControllerTable->fanLevel_f / fanControllerTable->temperature_c;
q = 0 - m;
fanLevelSet_f = (m * temperatureC_f) + q;
}else if(temperatureC_f >= (fanControllerTable + 9)->temperature_c)
{
fanLevelSet_f = (fanControllerTable + 9)->fanLevel_f;
}else
{
for(int i = 0; i < (TABLE_SIZE/sizeof(TemperaturePoint)) - 1; i++)
{
if(temperatureC_f >= (fanControllerTable + i)->temperature_c && temperatureC_f <= (fanControllerTable + i + 1)->temperature_c)
{
float m = 0;
float q = 0;
m = ((fanControllerTable + i + 1)->fanLevel_f - (fanControllerTable + i)->fanLevel_f ) / ((fanControllerTable + i + 1)->temperature_c - (fanControllerTable + i)->temperature_c);
q = (fanControllerTable + i)->fanLevel_f - (m * (fanControllerTable + i)->temperature_c);
fanLevelSet_f = (m * temperatureC_f) + q;
break;
}
}
}
// Always update fan speed for immediate response
rs = fanControllerSetRotationSpeedLevel(&fc, fanLevelSet_f);
if(R_FAILED(rs))
{
WriteLog("fanControllerSetRotationSpeedLevel error");
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
// Use responsive sleep time when awake (250ms)
svcSleepThread(awakeSleepTime);
}
fanControllerClose(&fc);
}
void StartFanControllerThread()
{
if(R_FAILED(threadStart(&FanControllerThread)))
{
WriteLog("Error starting FanControllerThread");
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
}
void CloseFanControllerThread()
{
Result rs;
fanControllerThreadExit = true;
rs = threadWaitForExit(&FanControllerThread);
if(R_FAILED(rs))
{
WriteLog("Error waiting fanControllerThread");
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
threadClose(&FanControllerThread);
fanControllerThreadExit = false;
free(fanControllerTable);
}
void WaitFanController()
{
if(R_FAILED(threadWaitForExit(&FanControllerThread)))
{
WriteLog("Error waiting fanControllerThread");
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <sys/stat.h>
#include <sys/syslimits.h>
#include <switch.h>
#define LOG_DIR "./config/hoc-clk/"
#define LOG_FILE "./config/hoc-clk/fan_log.txt"
#define CONFIG_DIR "./config/hoc-clk/"
#define CONFIG_FILE "./config/hoc-clk/config.dat"
#define TABLE_SIZE sizeof(TemperaturePoint) * 10
typedef struct
{
int temperature_c;
float fanLevel_f;
} TemperaturePoint;
void WriteConfigFile(TemperaturePoint *table);
void ReadConfigFile(TemperaturePoint **table_out);
void InitFanController(TemperaturePoint *table);
void FanControllerThreadFunction(void*);
void StartFanControllerThread();
void CloseFanControllerThread();
void WaitFanController();
void WriteLog(char *buffer);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,82 @@
#ifndef I2C_H
#define I2C_H
#include <switch.h>
Result I2cReadRegHandler16(u8 reg, I2cDevice dev, u16 *out)
{
struct readReg {
u8 send;
u8 sendLength;
u8 sendData;
u8 receive;
u8 receiveLength;
};
I2cSession _session;
Result res = i2cOpenSession(&_session, dev);
if (res)
return res;
u16 val;
struct readReg readRegister = {
.send = 0 | (I2cTransactionOption_Start << 6),
.sendLength = sizeof(reg),
.sendData = reg,
.receive = 1 | (I2cTransactionOption_All << 6),
.receiveLength = sizeof(val),
};
res = i2csessionExecuteCommandList(&_session, &val, sizeof(val), &readRegister, sizeof(readRegister));
if (res)
{
i2csessionClose(&_session);
return res;
}
*out = val;
i2csessionClose(&_session);
return 0;
}
Result I2cReadRegHandler8(u8 reg, I2cDevice dev, u8 *out)
{
struct readReg {
u8 send;
u8 sendLength;
u8 sendData;
u8 receive;
u8 receiveLength;
};
I2cSession _session;
Result res = i2cOpenSession(&_session, dev);
if (res)
return res;
u8 val;
struct readReg readRegister = {
.send = 0 | (I2cTransactionOption_Start << 6),
.sendLength = sizeof(reg),
.sendData = reg,
.receive = 1 | (I2cTransactionOption_All << 6),
.receiveLength = sizeof(val),
};
res = i2csessionExecuteCommandList(&_session, &val, sizeof(val), &readRegister, sizeof(readRegister));
if (res)
{
i2csessionClose(&_session);
return res;
}
*out = val;
i2csessionClose(&_session);
return 0;
}
#endif

View File

@@ -9,6 +9,8 @@
#include "errors.h"
#include "file_utils.h"
#include "clock_manager.h"
class ReverseNXSync {
public:
ReverseNXSync ();
@@ -30,4 +32,4 @@ protected:
ReverseNXMode GetToolModeFromPatch(const char* patch_path);
ReverseNXMode RecheckToolMode();
};
};

View File

@@ -20,7 +20,7 @@
#include "process_management.h"
#include "clock_manager.h"
#include "ipc_service.h"
#include "fancontrol.h"
#define INNER_HEAP_SIZE 0x30000
extern "C"
@@ -63,12 +63,24 @@ extern "C"
hosversionSet(MAKEHOSVERSION(fw.major, fw.minor, fw.micro));
setsysExit();
}
rc = fanInitialize();
if (R_FAILED(rc))
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
rc = i2cInitialize();
if (R_FAILED(rc))
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen));
}
void __appExit(void)
{
smExit();
}
CloseFanControllerThread();
fanExit();
i2cExit();
fsExit();
fsdevUnmountAll();
}
}
int main(int argc, char** argv)
@@ -95,6 +107,10 @@ int main(int argc, char** argv)
clockMgr->SetRunning(true);
clockMgr->GetConfig()->SetEnabled(true);
ipcSrv->SetRunning(true);
TemperaturePoint *table;
ReadConfigFile(&table);
InitFanController(table);
StartFanControllerThread();
while (clockMgr->Running())
{
@@ -121,5 +137,6 @@ int main(int argc, char** argv)
FileUtils::LogLine("Exit");
svcSleepThread(1000000ULL);
FileUtils::Exit();
return 0;
}

View File

@@ -0,0 +1,98 @@
/*
* SOC/PCB Temperature driver for Nintendo Switch's TI TMP451
*
* Copyright (c) 2018 CTCaer
*
* 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/>.
*/
/*
* Modified by: MasaGratoR
*/
//#include <utils/types.h>
#include "i2c.h"
//#define TMP451_I2C_ADDR 0x4C
#define TMP451_PCB_TEMP_REG 0x00
#define TMP451_SOC_TEMP_REG 0x01
/*
#define TMP451_CONFIG_REG 0x09
#define TMP451_CNV_RATE_REG 0x0A
*/
#define TMP451_SOC_TEMP_DEC_REG 0x10
#define TMP451_PCB_TEMP_DEC_REG 0x15
/*
#define TMP451_SOC_TMP_OFH_REG 0x11
#define TMP451_SOC_TMP_OFL_REG 0x12
*/
// If input is false, the return value is packed. MSByte is the integer in oC
// and the LSByte is the decimal point truncated to 2 decimal places.
// Otherwise it's an integer oC.
/*
u16 tmp451_get_soc_temp(bool integer);
u16 tmp451_get_pcb_temp(bool integer);
void tmp451_init();
void tmp451_end();
*/
Result Tmp451ReadReg(u8 reg, u8 *out)
{
u8 data = 0;
Result res = I2cReadRegHandler8(reg, I2cDevice_Tmp451, &data);
if (R_FAILED(res))
{
return res;
}
*out = data;
return res;
}
Result Tmp451GetSocTemp(float* temperature) {
u8 integer = 0;
u8 decimals = 0;
Result rc = Tmp451ReadReg(TMP451_SOC_TEMP_REG, &integer);
if (R_FAILED(rc))
return rc;
rc = Tmp451ReadReg(TMP451_SOC_TEMP_DEC_REG, &decimals);
if (R_FAILED(rc))
return rc;
decimals = ((u16)(decimals >> 4) * 625) / 100;
*temperature = (float)(integer) + ((float)(decimals) / 100);
return rc;
}
Result Tmp451GetPcbTemp(float* temperature) {
u8 integer = 0;
u8 decimals = 0;
Result rc = Tmp451ReadReg(TMP451_PCB_TEMP_REG, &integer);
if (R_FAILED(rc))
return rc;
rc = Tmp451ReadReg(TMP451_PCB_TEMP_DEC_REG, &decimals);
if (R_FAILED(rc))
return rc;
decimals = ((u16)(decimals >> 4) * 625) / 100;
*temperature = (float)(integer) + ((float)(decimals) / 100);
return rc;
}