Files
Horizon-OC/Source/sys-clk/sysmodule/src/fancontrol.c
2025-11-09 16:14:52 -05:00

250 lines
6.8 KiB
C

#include "fancontrol.h"
#include "tmp451.h"
//Fan curve table
TemperaturePoint defaultTable[] =
{
{ .temperature_c = 25.0, .fanLevel_f = 0.00 },
{ .temperature_c = 30.0, .fanLevel_f = 0.00 },
{ .temperature_c = 35.0, .fanLevel_f = 0.00 },
{ .temperature_c = 40.0, .fanLevel_f = 0.00 },
{ .temperature_c = 45.0, .fanLevel_f = 0.30 },
{ .temperature_c = 50.0, .fanLevel_f = 0.50 },
{ .temperature_c = 55.0, .fanLevel_f = 0.60 },
{ .temperature_c = 60.0, .fanLevel_f = 0.85 },
{ .temperature_c = 65.0, .fanLevel_f = 0.95 },
{ .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 = 1000000000ULL; // 1 second when awake
u64 sleepSleepTime = 10000000000ULL; // 10 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 40 iterations (~10 seconds) to reduce overhead
sleepCheckCounter++;
if(sleepCheckCounter >= 40)
{
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));
}
}