From c6ac1d60bff89ef3109fb8ad27ee9c00d72e2552 Mon Sep 17 00:00:00 2001 From: souldbminersmwc Date: Fri, 19 Dec 2025 19:30:52 -0500 Subject: [PATCH] sysclk: add charge current override --- Source/sys-clk/common/include/sysclk/config.h | 10 +- .../sys-clk/overlay/src/ui/gui/misc_gui.cpp | 26 +++ .../sys-clk/sysmodule/src/clock_manager.cpp | 6 + Source/sys-clk/sysmodule/src/i2c_reg.cpp | 186 ++++++++++++++++++ Source/sys-clk/sysmodule/src/i2c_reg.h | 57 ++++++ 5 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 Source/sys-clk/sysmodule/src/i2c_reg.cpp create mode 100644 Source/sys-clk/sysmodule/src/i2c_reg.h diff --git a/Source/sys-clk/common/include/sysclk/config.h b/Source/sys-clk/common/include/sysclk/config.h index 52bf28cf..345964a1 100644 --- a/Source/sys-clk/common/include/sysclk/config.h +++ b/Source/sys-clk/common/include/sysclk/config.h @@ -63,6 +63,8 @@ typedef enum { HocClkConfigValue_KipEditing, HocClkConfigValue_KipFileName, + HorizonOCConfigValue_BatteryChargeCurrent, + KipConfigValue_custRev, KipConfigValue_mtcConf, KipConfigValue_hpMode, @@ -231,6 +233,9 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr case HocClkConfigValue_KipFileName: return pretty ? "KIP File Name" : "kip_file_name"; + case HorizonOCConfigValue_BatteryChargeCurrent: + return pretty ? "Battery Charge Current" : "bat_charge_current"; + // KIP config values case KipConfigValue_custRev: return pretty ? "Custom Revision" : "kip_cust_rev"; @@ -423,6 +428,8 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val) return 8600ULL; case HocClkConfigValue_LiteTDPLimit: return 6400ULL; + case HorizonOCConfigValue_BatteryChargeCurrent: + return 0ULL; default: return 0ULL; } @@ -548,7 +555,8 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in case KipConfigValue_g_volt_e_1036800: case KipConfigValue_g_volt_e_1075200: return true; - + case HorizonOCConfigValue_BatteryChargeCurrent: + return ((input >= 1024) && (input <= 3072)) || !input; default: return false; } diff --git a/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp index b9a544e6..bda55719 100644 --- a/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp @@ -995,9 +995,35 @@ void MiscGui::listUI() addConfigButton(KipConfigValue_g_volt_e_1075200, "GPU Voltage (1075.2MHz)", ValueRange(0, 0, 0, "0", 1), "Voltage", &EgpuVmaxThresholds, {}, eGpuVolts, false); } + std::vector chargerCurrents = { + NamedValue("Disabled", 0), + NamedValue("1024mA", 1024), + NamedValue("1280mA", 1280), + NamedValue("1536mA", 1536), + NamedValue("1792mA", 1792), + NamedValue("2048mA", 2048), + NamedValue("2304mA", 2304), + NamedValue("2560mA", 2560), + NamedValue("2816mA", 2816), + NamedValue("3072mA", 3072), + }; + + ValueThresholds chargerThresholds(2048, 2560); + // ========== EXPERIMENTAL CATEGORY (BOTTOM) ========== this->listElement->addItem(new tsl::elm::CategoryHeader("Experimental")); + addConfigButton( + HorizonOCConfigValue_BatteryChargeCurrent, + "Charge Current Override", + ValueRange(0, 0, 1, "", 0), + "Charge Current Override", + &chargerThresholds, + {}, + chargerCurrents, + false + ); + addConfigToggle(HocClkConfigValue_HandheldGovernor, nullptr); } diff --git a/Source/sys-clk/sysmodule/src/clock_manager.cpp b/Source/sys-clk/sysmodule/src/clock_manager.cpp index 004a2a90..c1f10bc9 100644 --- a/Source/sys-clk/sysmodule/src/clock_manager.cpp +++ b/Source/sys-clk/sysmodule/src/clock_manager.cpp @@ -33,6 +33,8 @@ #include "errors.h" #include "ipc_service.h" #include "kip.h" +#include "i2c_reg.h" + #define HOSPPC_HAS_BOOST (hosversionAtLeast(7,0,0)) ClockManager *ClockManager::instance = NULL; @@ -254,6 +256,10 @@ void ClockManager::Tick() Result rc = apmExtGetCurrentPerformanceConfiguration(&mode); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + if(this->config->GetConfigValue(HorizonOCConfigValue_BatteryChargeCurrent)) { + I2c_Bq24193_SetFastChargeCurrentLimit(this->config->GetConfigValue(HorizonOCConfigValue_BatteryChargeCurrent)); + } + 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)) { diff --git a/Source/sys-clk/sysmodule/src/i2c_reg.cpp b/Source/sys-clk/sysmodule/src/i2c_reg.cpp new file mode 100644 index 00000000..8ed9f4db --- /dev/null +++ b/Source/sys-clk/sysmodule/src/i2c_reg.cpp @@ -0,0 +1,186 @@ +#include "i2c_reg.h" + +Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) { + // ams::fatal::srv::StopSoundTask::StopSound() + // I2C Bus Communication Reference: https://www.ti.com/lit/an/slva704/slva704.pdf + struct { + u8 reg; + u8 val; + } __attribute__((packed)) cmd; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + cmd.val = val; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + i2csessionClose(&_session); + return res; +} + +Result I2cRead_OutU8(I2cDevice dev, u8 reg, u8 *out) { + struct { u8 reg; } __attribute__((packed)) cmd; + struct { u8 val; } __attribute__((packed)) rec; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + if (res) { + i2csessionClose(&_session); + return res; + } + + res = i2csessionReceiveAuto(&_session, &rec, sizeof(rec), I2cTransactionOption_All); + i2csessionClose(&_session); + if (res) { + return res; + } + + *out = rec.val; + return 0; +} + +Result I2cRead_OutU16(I2cDevice dev, u8 reg, u16 *out) { + struct { u8 reg; } __attribute__((packed)) cmd; + struct { u16 val; } __attribute__((packed)) rec; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + if (res) { + i2csessionClose(&_session); + return res; + } + + res = i2csessionReceiveAuto(&_session, &rec, sizeof(rec), I2cTransactionOption_All); + i2csessionClose(&_session); + if (res) { + return res; + } + + *out = rec.val; + return 0; +} + +float I2c_Max17050_GetBatteryCurrent() { + u16 val; + Result res = I2cRead_OutU16(I2cDevice_Max17050, MAX17050_CURRENT_REG, &val); + if (res) + return 0.f; + + const float SenseResistor = 5.; // in uOhm + const float CGain = 1.99993; + return (s16)val * (1.5625 / (SenseResistor * CGain)); +} + +u32 I2c_BuckConverter_MultiplierToMvOut(const I2c_BuckConverter_Domain* domain, u8 multiplier) { + return (domain->uv_min + domain->uv_step * multiplier) / 1000; +} + +u8 I2c_BuckConverter_MvOutToMultiplier(const I2c_BuckConverter_Domain* domain, u32 mvolt) { + u32 uvolt = mvolt * 1000; + if (uvolt < domain->uv_min) + uvolt = domain->uv_min; + if (uvolt > domain->uv_max) + uvolt = domain->uv_max; + + return (uvolt - domain->uv_min) / domain->uv_step; +} + +u32 I2c_BuckConverter_GetMvOut(const I2c_BuckConverter_Domain* domain) { + u8 val; + // Retry 5 times if received POR value + for (int i = 0; i < 5; i++) { + if (R_FAILED(I2cRead_OutU8(domain->device, domain->reg, &val))) + return 0u; + + // Wait 1us + svcSleepThread(1E3); + + if (!domain->por_val || val != domain->por_val) + break; + } + return I2c_BuckConverter_MultiplierToMvOut(domain, val & domain->volt_mask); +} + +Result I2c_BuckConverter_SetMvOut(const I2c_BuckConverter_Domain* domain, u32 mvolt) { + u8 val; + Result res = I2cRead_OutU8(domain->device, domain->reg, &val); + if (R_FAILED(res)) + return res; + + u8 multiplier = I2c_BuckConverter_MvOutToMultiplier(domain, mvolt); + val &= ~domain->volt_mask; + val |= multiplier & domain->volt_mask; + + res = I2cSet_U8(domain->device, domain->reg, val); + if (R_FAILED(res)) + return res; + + // 5ms Ramp delay + svcSleepThread(5E6); + u8 new_val; + res = I2cRead_OutU8(domain->device, domain->reg, &new_val); + if (R_FAILED(res)) + return res; + if (new_val != val) + return -1; + + return 0; +} + +u8 I2c_Bq24193_Convert_mA_Raw(u32 ma) { + // Adjustment is required + u8 raw = 0; + + if (ma > MA_RANGE_MAX) // capping + ma = MA_RANGE_MAX; + + bool pct20 = ma <= (MA_RANGE_MIN - 64); + if (pct20) { + ma = ma * 5; + raw |= 0x1; + } + + ma -= ma % 100; // round to 100 + ma -= (MA_RANGE_MIN - 64); // ceiling + raw |= (ma >> 6) << 2; + + return raw; +}; + +u32 I2c_Bq24193_Convert_Raw_mA(u8 raw) { + // No adjustment is allowed + u32 ma = (((raw >> 2)) << 6) + MA_RANGE_MIN; + + bool pct20 = raw & 1; + if (pct20) + ma = ma * 20 / 100; + + return ma; +}; + +Result I2c_Bq24193_GetFastChargeCurrentLimit(u32 *ma) { + u8 raw; + Result res = I2cRead_OutU8(I2cDevice_Bq24193, BQ24193_CHARGE_CURRENT_CONTROL_REG, &raw); + if (res) + return res; + + *ma = I2c_Bq24193_Convert_Raw_mA(raw); + return 0; +} + +Result I2c_Bq24193_SetFastChargeCurrentLimit(u32 ma) { + u8 raw = I2c_Bq24193_Convert_mA_Raw(ma); + return I2cSet_U8(I2cDevice_Bq24193, BQ24193_CHARGE_CURRENT_CONTROL_REG, raw); +} \ No newline at end of file diff --git a/Source/sys-clk/sysmodule/src/i2c_reg.h b/Source/sys-clk/sysmodule/src/i2c_reg.h new file mode 100644 index 00000000..1a85dd36 --- /dev/null +++ b/Source/sys-clk/sysmodule/src/i2c_reg.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +// To use i2c service, sm and i2c should be intialized via smInitialize() and i2cInitialize(). + +Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val); + +Result I2cRead_OutU8(I2cDevice dev, u8 reg, u8 *out); +Result I2cRead_OutU16(I2cDevice dev, u8 reg, u16 *out); + +// Max17050 fuel gauge +float I2c_Max17050_GetBatteryCurrent(); + +const u8 MAX17050_CURRENT_REG = 0x0A; + +// Buck Converter +typedef enum I2c_BuckConverter_Reg { + I2c_Max77620_SD1VOLT_REG = 0x17, // Used for Erista DDR VDDQ+VDD2 / Mariko VDD2 + I2c_Max77621_VOLT_REG = 0x00, + I2c_Max77812_CPUVOLT_REG = 0x26, + I2c_Max77812_GPUVOLT_REG = 0x23, + I2c_Max77812_MEMVOLT_REG = 0x25, // Master 3 (GPU 1 + 2, DRAM 3, CPU 4), used for Mariko VDDQ +} I2c_BuckConverter_Reg; + +typedef struct I2c_BuckConverter_Domain { + I2cDevice device; + I2c_BuckConverter_Reg reg; + u8 volt_mask; + u32 uv_step; + u32 uv_min; + u32 uv_max; + u8 por_val; +} I2c_BuckConverter_Domain; + +const I2c_BuckConverter_Domain I2c_Erista_CPU = { I2cDevice_Max77621Cpu, I2c_Max77621_VOLT_REG, 0x7F, 6250, 606250, 1400000, }; +const I2c_BuckConverter_Domain I2c_Erista_GPU = { I2cDevice_Max77621Gpu, I2c_Max77621_VOLT_REG, 0x7F, 6250, 606250, 1400000, }; +const I2c_BuckConverter_Domain I2c_Erista_DRAM = { I2cDevice_Max77620Pmic, I2c_Max77620_SD1VOLT_REG, 0x7F, 12500, 600000, 1250000, }; +const I2c_BuckConverter_Domain I2c_Mariko_CPU = { I2cDevice_Max77812_2, I2c_Max77812_CPUVOLT_REG, 0xFF, 5000, 250000, 1525000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_GPU = { I2cDevice_Max77812_2, I2c_Max77812_GPUVOLT_REG, 0xFF, 5000, 250000, 1525000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_DRAM_VDDQ = { I2cDevice_Max77812_2, I2c_Max77812_MEMVOLT_REG, 0xFF, 5000, 250000, 650000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_DRAM_VDD2 = { I2cDevice_Max77620Pmic, I2c_Max77620_SD1VOLT_REG, 0x7F, 12500, 600000, 1250000, }; + +u32 I2c_BuckConverter_GetMvOut(const I2c_BuckConverter_Domain* domain); +Result I2c_BuckConverter_SetMvOut(const I2c_BuckConverter_Domain* domain, u32 mvolt); + +// Bq24193 Battery management +u32 I2c_Bq24193_Convert_Raw_mA(u8 raw); +u8 I2c_Bq24193_Convert_mA_Raw(u32 ma); + +Result I2c_Bq24193_GetFastChargeCurrentLimit(u32 *ma); +Result I2c_Bq24193_SetFastChargeCurrentLimit(u32 ma); + +const u32 MA_RANGE_MIN = 512; +const u32 MA_RANGE_MAX = 4544; + +const u8 BQ24193_CHARGE_CURRENT_CONTROL_REG = 0x2; \ No newline at end of file