#define TESLA_INIT_IMPL // If you have more than one file using the tesla header, only define this in the main one #include // The Tesla Header #include #include #include #include #include #include #include "rgltr.h" #define IS_BAT_CHARGE_ON ((_batteryChargeInfoFields->unk_x14 >> 8) & 1) #define IS_SLOW_CHARGE_ON (_batteryChargeInfoFields->ChargeCurrentLimit < 1024) static 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); } Service* rgltrGetServiceSession(void) { return &g_rgltrSrv; } Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id) { const u32 in = module_id; return serviceDispatchIn(&g_rgltrSrv, 0, in, .out_num_objects = 1, .out_objects = &session_out->s, ); } Result rgltrGetPowerModuleNumLimit(u32 *out) { return serviceDispatchOut(&g_rgltrSrv, 3, *out); } void rgltrCloseSession(RgltrSession* session) { serviceClose(&session->s); } Result rgltrGetVoltageEnabled(RgltrSession* session, u32 *out) { return serviceDispatchOut(&session->s, 2, *out); } Result rgltrGetVoltage(RgltrSession* session, u32 *out_volt) { return serviceDispatchOut(&session->s, 4, *out_volt); } ///* Notes VoltageAvg // // Vavg time = 175.8ms x 2^(6+VOLT), default: VOLT = 2 (Vavg time = 45s) // ///End of Notes extern "C" bool is_mariko(); extern "C" void clk_check(); typedef struct { u32 cpu_out_hz; u32 gpu_out_hz; u32 emc_out_hz; u32 cpu_out_volt; u32 gpu_out_volt; u32 emc_out_volt; } ClkFields; bool is_mariko() { u64 hardware_type = 0; splInitialize(); splGetConfig(SplConfigItem_HardwareType, &hardware_type); splExit(); switch(hardware_type) { case 0: //Icosa case 1: //Copper return false; case 2: //Hoag case 3: //Iowa case 4: //Calcio case 5: //Aula return true; default: return false; } } void clk_check(ClkFields *out, bool mariko) { int res = 0; ClkrstSession clkrstSession; RgltrSession rgltrSession; res = clkrstInitialize(); if(R_FAILED(res)) { fatalThrow(res); } res = rgltrInitialize(); if(R_FAILED(res)) { fatalThrow(res); } rgltrOpenSession(&rgltrSession, mariko ? PcvPowerDomainId_Max77812_Cpu : PcvPowerDomainId_Max77621_Cpu); clkrstOpenSession(&clkrstSession, PcvModuleId_CpuBus, 3); rgltrGetVoltage(&rgltrSession, &out->cpu_out_volt); clkrstGetClockRate(&clkrstSession, &out->cpu_out_hz); clkrstCloseSession(&clkrstSession); rgltrCloseSession(&rgltrSession); rgltrOpenSession(&rgltrSession, mariko ? PcvPowerDomainId_Max77812_Gpu : PcvPowerDomainId_Max77621_Gpu); clkrstOpenSession(&clkrstSession, PcvModuleId_GPU, 3); rgltrGetVoltage(&rgltrSession, &out->gpu_out_volt); clkrstGetClockRate(&clkrstSession, &out->gpu_out_hz); clkrstCloseSession(&clkrstSession); rgltrCloseSession(&rgltrSession); rgltrOpenSession(&rgltrSession, mariko ? PcvPowerDomainId_Max77812_Dram : PcvPowerDomainId_Max77620_Sd1); clkrstOpenSession(&clkrstSession, PcvModuleId_EMC, 3); rgltrGetVoltage(&rgltrSession, &out->emc_out_volt); clkrstGetClockRate(&clkrstSession, &out->emc_out_hz); clkrstCloseSession(&clkrstSession); rgltrCloseSession(&rgltrSession); clkrstExit(); rgltrExit(); } typedef enum { NoHub = BIT(0), //If hub is disconnected Rail = BIT(8), //At least one Joy-con is charging from rail SPDSRC = BIT(12), //OTG ACC = BIT(16) //Accessory } BatteryChargeInfoFieldsFlags; typedef enum { NewPDO = 1, //Received new Power Data Object NoPD = 2, //No Power Delivery source is detected AcceptedRDO = 3 //Received and accepted Request Data Object } BatteryChargeInfoFieldsPDControllerState; //BM92T series const char* strPDControllerState[] = { "Received new PDO", "No PD Source", "Received/Accpted RDO" }; typedef enum { None = 0, PD = 1, TypeC_1500mA = 2, TypeC_3000mA = 3, DCP = 4, CDP = 5, SDP = 6, Apple_500mA = 7, Apple_1000mA = 8, Apple_2000mA = 9 } BatteryChargeInfoFieldsChargerType; const char* strChargerType[] = { "None", "PD", "USB-C@1.5A", "USB-C@3.0A", "USB-DCP", "USB-CDP", "USB-SDP", "Apple@0.5A", "Apple@1.0A", "Apple@2.0A", }; typedef enum { Sink = 1, Source = 2 } BatteryChargeInfoFieldsPowerRole; const char* strPowerRole[] = { "Sink", "Source", }; 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 BatteryChargeInfoFieldsPDControllerState PDControllerState; //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) BatteryChargeInfoFieldsPowerRole PowerRole; BatteryChargeInfoFieldsChargerType ChargerType; int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV int32_t ChargerCurrentLimit; //Charger and external device current limit in mA BatteryChargeInfoFieldsFlags Flags; //Unknown flags } BatteryChargeInfoFields; Result psmGetBatteryChargeInfoFields(Service* psmService, BatteryChargeInfoFields *out) { return serviceDispatchOut(psmService, 17, *out); } static Result psmEnableBatteryCharging(Service* psmService) { return serviceDispatch(psmService, 2); } static Result psmDisableBatteryCharging(Service* psmService) { return serviceDispatch(psmService, 3); } static Result psmDisableSlowCharging(Service* psmService) { return serviceDispatch(psmService, 10); } static Result psmEnableSlowCharging(Service* psmService) { return serviceDispatch(psmService, 11); } FanController g_ICon; float rotationSpeedLevel = 0; unsigned int changeSlowChargingState = 0; unsigned int changeDisableChargingState = 0; bool threadexit = false; int32_t socTempMili = 0; char Print_x[800]; Thread t0; bool IsEnoughPowerSupplied; void Loop(void*) { static Service* psmService = psmGetServiceSession(); static BatteryChargeInfoFields* _batteryChargeInfoFields = new BatteryChargeInfoFields; static ClkFields* _clkFields = new ClkFields; while (threadexit == false) { if(changeSlowChargingState) { changeSlowChargingState = 0; if(IS_SLOW_CHARGE_ON) psmDisableSlowCharging(psmService); else psmEnableSlowCharging(psmService); } if(changeDisableChargingState) { changeDisableChargingState = 0; if(IS_BAT_CHARGE_ON) psmDisableBatteryCharging(psmService); else psmEnableBatteryCharging(psmService); } psmGetBatteryChargeInfoFields(psmService, _batteryChargeInfoFields); clk_check(_clkFields, is_mariko()); tsGetTemperatureMilliC(TsLocation_External, &socTempMili); psmIsEnoughPowerSupplied(&IsEnoughPowerSupplied); fanControllerGetRotationSpeedLevel(&g_ICon, &rotationSpeedLevel); snprintf(Print_x, sizeof(Print_x), "Current Limit: %u mA IN, %u mA OUT" "\nBattery Charg. Limit: %u mA, %u mV" "\nunk_x10: 0x%08" PRIx32 "\nunk_x14: 0x%08" PRIx32 "\nPD Contr. State: %s (%u)" "\nBattery Temp.: %.2f\u00B0C" "\nRaw Battery Charge: %.2f%%" "\nVoltage Avg: %u mV" "\nBattery Age: %.2f%%" "\nPower Role: %s (%u)" "\nCharger Type: %s (%u)" "\nCharger Limit: %u mV, %u mA" "\nunk_x3c: 0x%08" PRIx32 "\nEnough Power Supplied: %s" "\n" "\nCPU Clock: %6.1f MHz" "\nCPU Volt : %6.1f mV\n" "\nGPU Clock: %6.1f MHz" "\nGPU Volt : %6.1f mV\n" "\nEMC Clock: %6.1f MHz" "\nEMC Volt : %6.1f mV\n" "\nSoC Temp : %2.2f \u00B0C" "\nFan Speed: %2.2f %%\n" "\nL-Stick: Slow Charging(0.5A) (%s)" "\nR-Stick: Disable Charging (%s)" , _batteryChargeInfoFields->InputCurrentLimit, _batteryChargeInfoFields->VBUSCurrentLimit, _batteryChargeInfoFields->ChargeCurrentLimit, _batteryChargeInfoFields->ChargeVoltageLimit, _batteryChargeInfoFields->unk_x10, _batteryChargeInfoFields->unk_x14, strPDControllerState[_batteryChargeInfoFields->PDControllerState], _batteryChargeInfoFields->PDControllerState, (float)_batteryChargeInfoFields->BatteryTemperature / 1000, (float)_batteryChargeInfoFields->RawBatteryCharge / 1000, _batteryChargeInfoFields->VoltageAvg, (float)_batteryChargeInfoFields->BatteryAge / 1000, strPowerRole[_batteryChargeInfoFields->PowerRole], _batteryChargeInfoFields->PowerRole, strChargerType[_batteryChargeInfoFields->ChargerType], _batteryChargeInfoFields->ChargerType, _batteryChargeInfoFields->ChargerVoltageLimit, _batteryChargeInfoFields->ChargerCurrentLimit, (int32_t)_batteryChargeInfoFields->Flags, IsEnoughPowerSupplied ? "Yes" : "No", (double)_clkFields->cpu_out_hz / 1000000, (double)_clkFields->cpu_out_volt / 1000, (double)_clkFields->gpu_out_hz / 1000000, (double)_clkFields->gpu_out_volt / 1000, (double)_clkFields->emc_out_hz / 1000000, (double)_clkFields->emc_out_volt / 1000, (float)socTempMili / 1000, rotationSpeedLevel * 100, IS_SLOW_CHARGE_ON ? "ON" : "OFF", !IS_BAT_CHARGE_ON ? "ON" : "OFF" ); svcSleepThread(800'000'000); } delete _batteryChargeInfoFields; delete _clkFields; } class GuiTest : public tsl::Gui { public: GuiTest(u8 arg1, u8 arg2, bool arg3) { } // Called when this Gui gets loaded to create the UI // Allocate all elements on the heap. libtesla will make sure to clean them up when not needed anymore virtual tsl::elm::Element* createUI() override { // A OverlayFrame is the base element every overlay consists of. This will draw the default Title and Subtitle. // If you need more information in the header or want to change it's look, use a HeaderOverlayFrame. auto frame = new tsl::elm::OverlayFrame("InfoNX", APP_VERSION); // A list that can contain sub elements and handles scrolling auto list = new tsl::elm::List(); list->addItem(new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { renderer->drawString(Print_x, false, x, y+20, 15, renderer->a(0xFFFF)); }), 500); // Add the list to the frame for it to be drawn frame->setContent(list); // Return the frame to have it become the top level element of this Gui return frame; } // Called once every frame to update values virtual void update() override {} // Called once every frame to handle inputs not handled by other UI elements virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { if (keysHeld & HidNpadButton_A) { tsl::hlp::requestForeground(false); return true; } if (keysHeld & HidNpadButton_StickL) { changeSlowChargingState++; return true; } if (keysHeld & HidNpadButton_StickR) { changeDisableChargingState++; return true; } return false; // Return true here to singal the inputs have been consumed } }; class OverlayTest : public tsl::Overlay { public: // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys virtual void initServices() override { smInitialize(); psmInitialize(); tsInitialize(); fanInitialize(); fanOpenController(&g_ICon, 0x3D000001); threadCreate(&t0, Loop, NULL, NULL, 0x4000, 0x3F, -2); threadStart(&t0); } // Called at the start to initialize all services necessary for this Overlay virtual void exitServices() override { threadexit = true; threadWaitForExit(&t0); threadClose(&t0); fanControllerClose(&g_ICon); fanExit(); tsExit(); psmExit(); smExit(); } // Callet at the end to clean up all services previously initialized virtual void onShow() override {} // Called before overlay wants to change from invisible to visible state virtual void onHide() override {} // Called before overlay wants to change from visible to invisible state virtual std::unique_ptr loadInitialGui() override { return initially(1, 2, true); // Initial Gui to load. It's possible to pass arguments to it's constructor like this } }; int main(int argc, char **argv) { return tsl::loop(argc, argv); }