From 3cc79f4e113cd702c42dadf3681291e34e32f623 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 6 May 2019 23:08:28 -0700 Subject: [PATCH] boot: finish implementing CheckBatteryCharge --- .../boot/source/boot_battery_driver.cpp | 47 ++++ .../boot/source/boot_battery_driver.hpp | 4 + .../boot/source/boot_check_battery.cpp | 213 +++++++++++++++++- stratosphere/boot/source/boot_pmic_driver.cpp | 10 + stratosphere/boot/source/boot_pmic_driver.hpp | 1 + 5 files changed, 270 insertions(+), 5 deletions(-) diff --git a/stratosphere/boot/source/boot_battery_driver.cpp b/stratosphere/boot/source/boot_battery_driver.cpp index 8c9d607c5..a00dca2f5 100644 --- a/stratosphere/boot/source/boot_battery_driver.cpp +++ b/stratosphere/boot/source/boot_battery_driver.cpp @@ -307,3 +307,50 @@ Result BatteryDriver::IsBatteryRemoved(bool *out) { *out = (val & 0x0008) == 0x0008; return ResultSuccess; } + +Result BatteryDriver::GetTemperature(double *out) { + u16 val = 0; + Result rc = this->Read(Max17050Temperature, &val); + if (R_FAILED(rc)) { + return rc; + } + *out = static_cast(val) * double(0.00390625); + return ResultSuccess; +} + +Result BatteryDriver::GetAverageVCell(u32 *out) { + u16 val = 0; + Result rc = this->Read(Max17050AverageVCell, &val); + if (R_FAILED(rc)) { + return rc; + } + *out = (625 * u32(val >> 3)) / 1000; + return ResultSuccess; +} + +Result BatteryDriver::GetSocRep(double *out) { + u16 val = 0; + Result rc = this->Read(Max17050SocRep, &val); + if (R_FAILED(rc)) { + return rc; + } + *out = static_cast(val) * double(0.00390625); + return ResultSuccess; +} + +Result BatteryDriver::GetBatteryPercentage(size_t *out) { + double raw_charge; + Result rc = this->GetSocRep(&raw_charge); + if (R_FAILED(rc)) { + return rc; + } + int converted_percentage = (((raw_charge - 3.93359375) * 98.0) / 94.2304688) + 2.0; + if (converted_percentage < 1) { + *out = 1; + } else if (converted_percentage > 100) { + *out = 100; + } else { + *out = static_cast(converted_percentage); + } + return ResultSuccess; +} \ No newline at end of file diff --git a/stratosphere/boot/source/boot_battery_driver.hpp b/stratosphere/boot/source/boot_battery_driver.hpp index f12c81e46..8d46d8f9b 100644 --- a/stratosphere/boot/source/boot_battery_driver.hpp +++ b/stratosphere/boot/source/boot_battery_driver.hpp @@ -54,4 +54,8 @@ class BatteryDriver { public: Result InitializeBatteryParameters(); Result IsBatteryRemoved(bool *out); + Result GetTemperature(double *out); + Result GetAverageVCell(u32 *out); + Result GetSocRep(double *out); + Result GetBatteryPercentage(size_t *out); }; diff --git a/stratosphere/boot/source/boot_check_battery.cpp b/stratosphere/boot/source/boot_check_battery.cpp index 4f966c596..a2d2e17e2 100644 --- a/stratosphere/boot/source/boot_check_battery.cpp +++ b/stratosphere/boot/source/boot_check_battery.cpp @@ -25,6 +25,197 @@ enum CheckBatteryResult { CheckBatteryResult_Reboot = 2, }; +struct BatteryChargeParameters { + u32 temp_min; + u32 temp_low; + u32 temp_high; + u32 temp_max; + u32 allow_high_temp_charge_max_voltage; + u32 charge_voltage_limit_default; + u32 charge_voltage_limit_high_temp; + u32 allow_fast_charge_min_temp; + u32 allow_fast_charge_min_voltage; + u32 fast_charge_current_limit_default; + u32 fast_charge_current_limit_low_temp; + u32 fast_charge_current_limit_low_voltage; +}; + +static constexpr BatteryChargeParameters BatteryChargeParameters0 = { + .temp_min = 4, + .temp_low = 17, + .temp_high = 51, + .temp_max = 60, + .allow_high_temp_charge_max_voltage = 4050, + .charge_voltage_limit_default = 4208, + .charge_voltage_limit_high_temp = 3952, + .allow_fast_charge_min_voltage = 3320, + .fast_charge_current_limit_default = 0x800, + .fast_charge_current_limit_low_temp = 0x300, + .fast_charge_current_limit_low_voltage = 0x200, +}; + +static constexpr BatteryChargeParameters BatteryChargeParameters1 = { + .temp_min = 4, + .temp_low = 17, + .temp_high = 51, + .temp_max = 59, + .allow_high_temp_charge_max_voltage = 3984, + .charge_voltage_limit_default = 4208, + .charge_voltage_limit_high_temp = 3984, + .allow_fast_charge_min_voltage = 0, + .fast_charge_current_limit_default = 0x600, + .fast_charge_current_limit_low_temp = 0x240, + .fast_charge_current_limit_low_voltage = 0x600, +}; + +static constexpr BatteryChargeParameters BatteryChargeParameters2 = { + .temp_min = 4, + .temp_low = 17, + .temp_high = 51, + .temp_max = 59, + .allow_high_temp_charge_max_voltage = 4080, + .charge_voltage_limit_default = 4320, + .charge_voltage_limit_high_temp = 4080, + .allow_fast_charge_min_voltage = 0, + .fast_charge_current_limit_default = 0x680, + .fast_charge_current_limit_low_temp = 0x280, + .fast_charge_current_limit_low_voltage = 0x680, +}; + +static const BatteryChargeParameters *GetBatteryChargeParameters(u32 battery_version) { + switch (battery_version) { + case 0: + return &BatteryChargeParameters0; + case 1: + return &BatteryChargeParameters1; + case 2: + return &BatteryChargeParameters2; + default: + std::abort(); + } +} + +static void UpdateCharger(PmicDriver *pmic_driver, ChargerDriver *charger_driver, BatteryDriver *battery_driver, const BatteryChargeParameters *params, u32 charge_voltage_limit) { + double temperature; + u32 battery_voltage; + + if (R_FAILED(battery_driver->GetTemperature(&temperature)) || R_FAILED(battery_driver->GetAverageVCell(&battery_voltage))) { + pmic_driver->ShutdownSystem(); + } + + bool enable_charge = true; + if (temperature < double(params->temp_min)) { + enable_charge = false; + } else if (double(params->temp_high) <= temperature && temperature < double(params->temp_max)) { + if (battery_voltage < params->allow_high_temp_charge_max_voltage) { + charge_voltage_limit = std::min(charge_voltage_limit, params->charge_voltage_limit_high_temp); + } else { + enable_charge = false; + } + } else if (double(params->temp_max) <= temperature) { + enable_charge = false; + if (battery_voltage < params->allow_high_temp_charge_max_voltage) { + charge_voltage_limit = std::min(charge_voltage_limit, params->charge_voltage_limit_high_temp); + } + } + + u32 fast_charge_current_limit = params->fast_charge_current_limit_default; + if (temperature < double(params->temp_low)) { + fast_charge_current_limit = std::min(fast_charge_current_limit, params->fast_charge_current_limit_low_temp); + } + if (battery_voltage < params->allow_fast_charge_min_voltage) { + fast_charge_current_limit = std::min(fast_charge_current_limit, params->fast_charge_current_limit_low_voltage); + } + + if (R_FAILED(charger_driver->SetChargeEnabled(enable_charge))) { + pmic_driver->ShutdownSystem(); + } + if (R_FAILED(charger_driver->SetChargeVoltageLimit(charge_voltage_limit))) { + pmic_driver->ShutdownSystem(); + } + if (R_FAILED(charger_driver->SetFastChargeCurrentLimit(fast_charge_current_limit))) { + pmic_driver->ShutdownSystem(); + } +} + +static bool IsSufficientBattery(u32 battery_voltage, bool ac_ok) { + /* Nintendo has stuff for updating a static variable every 10 seconds here, but this seems, again, to be debug leftovers. */ + const u32 required_voltage = ac_ok ? 4000 : 3650; + return battery_voltage >= required_voltage; +} + +static CheckBatteryResult LoopCheckBattery(PmicDriver *pmic_driver, ChargerDriver *charger_driver, BatteryDriver *battery_driver, const BatteryChargeParameters *params, u32 charge_voltage_limit, bool reboot_on_power_button_pressed, bool succeed_on_sufficient_battery, bool shutdown_on_full_battery, bool can_show_battery_icon, bool can_show_charging_icon) { + bool is_showing_charging_icon = false; + ON_SCOPE_EXIT { + if (is_showing_charging_icon) { + Boot::EndShowChargingIcon(); + } + }; + + if (can_show_charging_icon) { + size_t battery_percentage; + if (R_FAILED(battery_driver->GetBatteryPercentage(&battery_percentage))) { + return CheckBatteryResult_Shutdown; + } + Boot::StartShowChargingIcon(battery_percentage, true); + is_showing_charging_icon = true; + } + + while (true) { + double battery_charge; + if (R_FAILED(battery_driver->GetSocRep(&battery_charge))) { + return CheckBatteryResult_Shutdown; + } + if (succeed_on_sufficient_battery && battery_charge >= 3.0) { + return CheckBatteryResult_Success; + } else if (shutdown_on_full_battery && battery_charge >= 99.0) { + return CheckBatteryResult_Shutdown; + } else { + /* Nintendo has logic for checking a value every 10 seconds. */ + /* They never do anything with this value though, so it's probably just leftovers from debug? */ + } + + bool ac_ok; + if (R_FAILED(pmic_driver->GetAcOk(&ac_ok))) { + return CheckBatteryResult_Shutdown; + } + + u32 battery_voltage; + if (R_FAILED(battery_driver->GetAverageVCell(&battery_voltage))) { + return CheckBatteryResult_Shutdown; + } + + if (succeed_on_sufficient_battery && IsSufficientBattery(battery_voltage, ac_ok)) { + return CheckBatteryResult_Success; + } + + if (!ac_ok) { + if (can_show_battery_icon && !is_showing_charging_icon) { + Boot::ShowLowBatteryIcon(); + } + return CheckBatteryResult_Shutdown; + } + + if (reboot_on_power_button_pressed) { + bool power_button_pressed; + if (R_FAILED(pmic_driver->GetPowerButtonPressed(&power_button_pressed))) { + return CheckBatteryResult_Shutdown; + } + if (power_button_pressed) { + return CheckBatteryResult_Reboot; + } + } + + if (can_show_battery_icon && !is_showing_charging_icon) { + Boot::StartShowChargingIcon(1, false); + is_showing_charging_icon = true; + } + + svcSleepThread(2'000'000'000ul); + UpdateCharger(pmic_driver, charger_driver, battery_driver, params, charge_voltage_limit); + } +} + void Boot::CheckBatteryCharge() { PmicDriver pmic_driver; BatteryDriver battery_driver; @@ -51,11 +242,23 @@ void Boot::CheckBatteryCharge() { pmic_driver.ShutdownSystem(); } - const u32 battery_version = Boot::GetBatteryVersion(); - - /* TODO: UpdateCharger(); */ - /* TODO: LoopCheckBattery(); */ - CheckBatteryResult check_result = CheckBatteryResult_Success; + const BatteryChargeParameters *params = GetBatteryChargeParameters(Boot::GetBatteryVersion()); + u32 charge_voltage_limit = params->charge_voltage_limit_default; + CheckBatteryResult check_result; + if (boot_reason == 4) { + if (R_FAILED(charger_driver.GetChargeVoltageLimit(&charge_voltage_limit))) { + pmic_driver.ShutdownSystem(); + } + UpdateCharger(&pmic_driver, &charger_driver, &battery_driver, params, charge_voltage_limit); + check_result = LoopCheckBattery(&pmic_driver, &charger_driver, &battery_driver, params, charge_voltage_limit, true, false, true, false, false); + } else { + UpdateCharger(&pmic_driver, &charger_driver, &battery_driver, params, charge_voltage_limit); + if (boot_reason == 1) { + check_result = LoopCheckBattery(&pmic_driver, &charger_driver, &battery_driver, params, charge_voltage_limit, true, true, false, true, true); + } else { + check_result = LoopCheckBattery(&pmic_driver, &charger_driver, &battery_driver, params, charge_voltage_limit, false, true, false, true, false); + } + } switch (check_result) { case CheckBatteryResult_Success: diff --git a/stratosphere/boot/source/boot_pmic_driver.cpp b/stratosphere/boot/source/boot_pmic_driver.cpp index 74482e1b0..7b3ce2d8e 100644 --- a/stratosphere/boot/source/boot_pmic_driver.cpp +++ b/stratosphere/boot/source/boot_pmic_driver.cpp @@ -57,6 +57,16 @@ Result PmicDriver::GetNvErc(u8 *out) { return Boot::ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr)); } +Result PmicDriver::GetPowerButtonPressed(bool *out) { + u8 power_intr; + Result rc = this->GetPowerIntr(&power_intr); + if (R_FAILED(rc)) { + return rc; + } + *out = (power_intr & 0x08) != 0; + return ResultSuccess; +} + Result PmicDriver::ShutdownSystem(bool reboot) { /* TODO: Implement this. */ std::abort(); diff --git a/stratosphere/boot/source/boot_pmic_driver.hpp b/stratosphere/boot/source/boot_pmic_driver.hpp index ec185fac6..cc8f84e90 100644 --- a/stratosphere/boot/source/boot_pmic_driver.hpp +++ b/stratosphere/boot/source/boot_pmic_driver.hpp @@ -42,4 +42,5 @@ class PmicDriver { Result GetAcOk(bool *out); Result GetPowerIntr(u8 *out); Result GetNvErc(u8 *out); + Result GetPowerButtonPressed(bool *out); }; \ No newline at end of file