/* * Copyright (c) Atmosphère-NX * * 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 . */ #include #include "boot_check_battery.hpp" #include "boot_battery_icons.hpp" #include "boot_boot_reason.hpp" #include "boot_pmic_driver.hpp" #include "boot_battery_driver.hpp" #include "boot_charger_driver.hpp" #include "boot_power_utils.hpp" namespace ams::boot { namespace { /* Value definitions. */ constexpr inline double BatteryLevelThresholdForBoot = 3.0; constexpr inline double BatteryLevelThresholdForFullCharge = 99.0; constexpr inline int BatteryVoltageThresholdConnected = 4000; constexpr inline int BatteryVoltageThresholdDisconnected = 3650; /* Types. */ enum class CheckBatteryResult { Success, Shutdown, Reboot, }; class BatteryChecker { private: boot::ChargerDriver &m_charger_driver; boot::BatteryDriver &m_battery_driver; const powctl::driver::impl::ChargeParameters &m_charge_parameters; powctl::driver::impl::ChargeArbiter m_charge_arbiter; powctl::ChargeCurrentState m_charge_current_state; int m_fast_charge_current_limit; int m_charge_voltage_limit; int m_battery_compensation; int m_voltage_clamp; TimeSpan m_charging_done_interval; bool m_has_start_time; TimeSpan m_start_time; private: bool IsChargeDone(); void UpdateChargeDoneCurrent(); void ApplyArbiterRule(); void PrintBatteryStatus(float raw_charge, int voltage, int voltage_threshold); CheckBatteryResult LoopCheckBattery(bool reboot_on_power_button_press, bool return_on_enough_battery, bool shutdown_on_full_battery, bool show_display, bool show_charging_display); void UpdateStartTime() { /* Update start time. */ m_start_time = os::ConvertToTimeSpan(os::GetSystemTick()); m_has_start_time = true; } public: BatteryChecker(boot::ChargerDriver &cd, boot::BatteryDriver &bd, const powctl::driver::impl::ChargeParameters &cp, int cvl) : m_charger_driver(cd), m_battery_driver(bd), m_charge_parameters(cp), m_charge_arbiter(cp.rules, cp.num_rules, cvl), m_charging_done_interval(TimeSpan::FromSeconds(2)), m_has_start_time(false) { /* Get parameters from charger. */ if (R_FAILED(m_charger_driver.GetChargeCurrentState(std::addressof(m_charge_current_state)))) { boot::ShutdownSystem(); } if (R_FAILED(m_charger_driver.GetFastChargeCurrentLimit(std::addressof(m_fast_charge_current_limit)))) { boot::ShutdownSystem(); } if (R_FAILED(m_charger_driver.GetChargeVoltageLimit(std::addressof(m_charge_voltage_limit)))) { boot::ShutdownSystem(); } if (R_FAILED(m_charger_driver.GetBatteryCompensation(std::addressof(m_battery_compensation)))) { boot::ShutdownSystem(); } if (R_FAILED(m_charger_driver.GetVoltageClamp(std::addressof(m_voltage_clamp)))) { boot::ShutdownSystem(); } /* Update start time. */ this->UpdateStartTime(); } CheckBatteryResult LoopCheckBattery(spl::BootReason boot_reason) { if (boot_reason == spl::BootReason_RtcAlarm2) { /* RTC Alarm 2 boot (QuasiOff) */ return this->LoopCheckBattery(true, false, true, false, false); } else if (boot_reason == spl::BootReason_AcOk) { /* ACOK boot */ return this->LoopCheckBattery(true, true, false, true, true); } else { /* Normal boot */ return this->LoopCheckBattery(false, true, false, true, false); } } void UpdateCharger(); }; void BatteryChecker::PrintBatteryStatus(float raw_charge, int voltage, int voltage_threshold) { /* TODO: Print charge/voltage/threshold. */ AMS_UNUSED(raw_charge, voltage, voltage_threshold); /* Get various battery metrics. */ int avg_current, current; float temp, voltage_fuel_gauge_percentage; if (R_FAILED(m_battery_driver.GetAverageCurrent(std::addressof(avg_current)))) { return; } if (R_FAILED(m_battery_driver.GetCurrent(std::addressof(current)))) { return; } if (R_FAILED(m_battery_driver.GetTemperature(std::addressof(temp)))) { return; } if (R_FAILED(m_battery_driver.GetVoltageFuelGaugePercentage(std::addressof(voltage_fuel_gauge_percentage)))) { return; } /* TODO: Print the things we just got. */ AMS_UNUSED(avg_current, current, temp, voltage_fuel_gauge_percentage); } bool BatteryChecker::IsChargeDone() { /* Get the charger status. */ boot::ChargerStatus charger_status; if (R_FAILED(m_charger_driver.GetChargerStatus(std::addressof(charger_status)))) { boot::ShutdownSystem(); } /* If charge status isn't done, we're not done. */ if (charger_status != boot::ChargerStatus_ChargeTerminationDone) { return false; } /* Return whether a done current of zero is acceptable. */ return m_charge_arbiter.IsBatteryDoneCurrentAcceptable(0); } void BatteryChecker::UpdateChargeDoneCurrent() { int done_current = 0; if (m_has_start_time && (os::ConvertToTimeSpan(os::GetSystemTick()) - m_start_time) >= m_charging_done_interval) { /* Get the current. */ if (R_FAILED(m_battery_driver.GetCurrent(std::addressof(done_current)))) { boot::ShutdownSystem(); } } else { /* Get the charger status. */ boot::ChargerStatus charger_status; if (R_FAILED(m_charger_driver.GetChargerStatus(std::addressof(charger_status)))) { boot::ShutdownSystem(); } /* If the charger status isn't done, don't update. */ if (charger_status != boot::ChargerStatus_ChargeTerminationDone) { return; } } /* Update done current. */ m_charge_arbiter.SetBatteryDoneCurrent(done_current); } void BatteryChecker::UpdateCharger() { /* Get the battery temperature. */ float temp; if (R_FAILED(m_battery_driver.GetTemperature(std::addressof(temp)))) { boot::ShutdownSystem(); } /* Update the temperature level. */ powctl::BatteryTemperatureLevel temp_level; if (temp < static_cast(m_charge_parameters.temp_min)) { temp_level = powctl::BatteryTemperatureLevel::TooLow; } else if (temp < static_cast(m_charge_parameters.temp_low)) { temp_level = powctl::BatteryTemperatureLevel::Low; } else if (temp < static_cast(m_charge_parameters.temp_high)) { temp_level = powctl::BatteryTemperatureLevel::Medium; } else if (temp < static_cast(m_charge_parameters.temp_max)) { temp_level = powctl::BatteryTemperatureLevel::High; } else { temp_level = powctl::BatteryTemperatureLevel::TooHigh; } m_charge_arbiter.SetBatteryTemperatureLevel(temp_level); /* Update average voltage. */ int avg_v_cell; if (R_FAILED(m_battery_driver.GetAverageVCell(std::addressof(avg_v_cell)))) { boot::ShutdownSystem(); } m_charge_arbiter.SetBatteryAverageVCell(avg_v_cell); /* Update voltage fuel gauge percentage. */ float vfgp; if (R_FAILED(m_battery_driver.GetVoltageFuelGaugePercentage(std::addressof(vfgp)))) { boot::ShutdownSystem(); } m_charge_arbiter.SetBatteryVoltageFuelGaugePercentage(vfgp); /* Update charge done current. */ this->UpdateChargeDoneCurrent(); /* Update arbiter power state. */ m_charge_arbiter.SetPowerState(powctl::PowerState::ShutdownChargeMain); /* Apply the newly selected rule. */ this->ApplyArbiterRule(); } void BatteryChecker::ApplyArbiterRule() { /* Get the selected rule. */ const auto *rule = m_charge_arbiter.GetSelectedRule(); AMS_ASSERT(rule != nullptr); /* Check if we need to perform charger initialization. */ const bool reinit_charger = rule->reinitialize_charger; const auto cur_charge_current_state = m_charge_current_state; /* Set the charger to not charging while we make changes. */ if (!reinit_charger || cur_charge_current_state != powctl::ChargeCurrentState_NotCharging) { if (R_FAILED(m_charger_driver.SetChargeCurrentState(powctl::ChargeCurrentState_NotCharging))) { boot::ShutdownSystem(); } m_charge_current_state = powctl::ChargeCurrentState_NotCharging; /* Update start time. */ this->UpdateStartTime(); } /* Process fast charge current limit when rule is smaller. */ const auto rule_fast_charge_current_limit = rule->fast_charge_current_limit; const auto cur_fast_charge_current_limit = m_fast_charge_current_limit; if (rule_fast_charge_current_limit < cur_fast_charge_current_limit) { if (R_FAILED(m_charger_driver.SetFastChargeCurrentLimit(rule_fast_charge_current_limit))) { boot::ShutdownSystem(); } m_fast_charge_current_limit = rule_fast_charge_current_limit; /* Update start time. */ this->UpdateStartTime(); } /* Process charge voltage limit when rule is smaller. */ const auto rule_charge_voltage_limit = std::min(rule->charge_voltage_limit, m_charge_arbiter.GetChargeVoltageLimit()); const auto cur_charge_voltage_limit = m_charge_voltage_limit; if (rule_charge_voltage_limit < cur_charge_voltage_limit) { if (R_FAILED(m_charger_driver.SetChargeVoltageLimit(rule_charge_voltage_limit))) { boot::ShutdownSystem(); } m_charge_voltage_limit = rule_charge_voltage_limit; /* Update start time. */ this->UpdateStartTime(); } /* Process battery compensation when rule is smaller. */ const auto rule_battery_compensation = rule->battery_compensation; const auto cur_battery_compensation = m_battery_compensation; if (rule_battery_compensation < cur_battery_compensation) { if (R_FAILED(m_charger_driver.SetBatteryCompensation(rule_battery_compensation))) { boot::ShutdownSystem(); } m_battery_compensation = rule_battery_compensation; /* Update start time. */ this->UpdateStartTime(); } /* Process voltage clamp when rule is smaller. */ const auto rule_voltage_clamp = rule->voltage_clamp; const auto cur_voltage_clamp = m_voltage_clamp; if (rule_voltage_clamp < cur_voltage_clamp) { if (R_FAILED(m_charger_driver.SetVoltageClamp(rule_voltage_clamp))) { boot::ShutdownSystem(); } m_voltage_clamp = rule_voltage_clamp; /* Update start time. */ this->UpdateStartTime(); } /* Process voltage clamp when rule is larger. */ if (rule_voltage_clamp > cur_voltage_clamp) { if (R_FAILED(m_charger_driver.SetVoltageClamp(rule_voltage_clamp))) { boot::ShutdownSystem(); } m_voltage_clamp = rule_voltage_clamp; /* Update start time. */ this->UpdateStartTime(); } /* Process battery compensation when rule is larger. */ if (rule_battery_compensation > cur_battery_compensation) { if (R_FAILED(m_charger_driver.SetBatteryCompensation(rule_battery_compensation))) { boot::ShutdownSystem(); } m_battery_compensation = rule_battery_compensation; /* Update start time. */ this->UpdateStartTime(); } /* Process fast charge current limit when rule is larger. */ if (rule_fast_charge_current_limit > cur_fast_charge_current_limit) { if (R_FAILED(m_charger_driver.SetFastChargeCurrentLimit(rule_fast_charge_current_limit))) { boot::ShutdownSystem(); } m_fast_charge_current_limit = rule_fast_charge_current_limit; /* Update start time. */ this->UpdateStartTime(); } /* Process charge voltage limit when rule is larger. */ if (rule_charge_voltage_limit > cur_charge_voltage_limit) { if (R_FAILED(m_charger_driver.SetChargeVoltageLimit(rule_charge_voltage_limit))) { boot::ShutdownSystem(); } m_charge_voltage_limit = rule_charge_voltage_limit; /* Update start time. */ this->UpdateStartTime(); } /* If we're not charging and we expect to reinitialize the charger, do so. */ if (cur_charge_current_state != powctl::ChargeCurrentState_Charging && reinit_charger) { if (R_FAILED(m_charger_driver.SetChargeCurrentState(powctl::ChargeCurrentState_Charging))) { boot::ShutdownSystem(); } m_charge_current_state = powctl::ChargeCurrentState_Charging; /* Update start time. */ this->UpdateStartTime(); } } CheckBatteryResult BatteryChecker::LoopCheckBattery(bool reboot_on_power_button_press, bool return_on_enough_battery, bool shutdown_on_full_battery, bool show_display, bool show_charging_display) { /* Ensure that if we show a charging icon, we stop showing it when we're done. */ bool is_showing_charging_icon = false; ON_SCOPE_EXIT { if (is_showing_charging_icon) { boot::EndShowChargingIcon(); is_showing_charging_icon = false; } }; /* Show the charging display, if we should. */ if (show_charging_display) { /* Get the raw battery charge. */ float raw_battery_charge; if (R_FAILED(m_battery_driver.GetChargePercentage(std::addressof(raw_battery_charge)))) { return CheckBatteryResult::Shutdown; } /* Display the battery with the appropriate percentage. */ const auto battery_charge = powctl::impl::ConvertBatteryChargePercentage(raw_battery_charge); boot::StartShowChargingIcon(battery_charge); is_showing_charging_icon = true; } /* Loop, checking the battery status. */ TimeSpan last_progress_time = TimeSpan(0); while (true) { /* Get the raw battery charge. */ float raw_battery_charge; if (R_FAILED(m_battery_driver.GetChargePercentage(std::addressof(raw_battery_charge)))) { return CheckBatteryResult::Shutdown; } /* Get the average vcell. */ int battery_voltage; if (R_FAILED(m_battery_driver.GetAverageVCell(std::addressof(battery_voltage)))) { return CheckBatteryResult::Shutdown; } /* Get whether we're connected to charger. */ bool ac_ok; if (R_FAILED((boot::PmicDriver().GetAcOk(std::addressof(ac_ok))))) { return CheckBatteryResult::Shutdown; } /* Decide on a battery voltage threshold. */ const auto battery_voltage_threshold = ac_ok ? BatteryVoltageThresholdConnected : BatteryVoltageThresholdDisconnected; /* Check if we should return. */ if (return_on_enough_battery) { if (raw_battery_charge >= BatteryLevelThresholdForBoot || battery_voltage >= battery_voltage_threshold) { this->PrintBatteryStatus(raw_battery_charge, battery_voltage, battery_voltage_threshold); return CheckBatteryResult::Success; } } /* Otherwise, check if we should shut down. */ if (shutdown_on_full_battery) { if (raw_battery_charge >= BatteryLevelThresholdForFullCharge || this->IsChargeDone()) { return CheckBatteryResult::Shutdown; } } /* Perform periodic printing. */ constexpr TimeSpan PrintProgressInterval = TimeSpan::FromSeconds(10); const auto cur_time = os::ConvertToTimeSpan(os::GetSystemTick()); if ((cur_time - last_progress_time) >= PrintProgressInterval) { last_progress_time = cur_time; this->PrintBatteryStatus(raw_battery_charge, battery_voltage, battery_voltage_threshold); } /* If we've gotten to this point, we have insufficient battery to boot. If we aren't charging, show low battery and shutdown. */ if (!ac_ok) { this->PrintBatteryStatus(raw_battery_charge, battery_voltage, battery_voltage_threshold); if (show_display && !is_showing_charging_icon) { boot::ShowLowBatteryIcon(); } return CheckBatteryResult::Shutdown; } /* Check if we should reboot due to a power button press. */ if (reboot_on_power_button_press) { /* Get the power button value. */ bool power_button_pressed; if (R_FAILED((boot::PmicDriver().GetPowerButtonPressed(std::addressof(power_button_pressed))))) { return CheckBatteryResult::Shutdown; } /* Handle the press (or not). */ if (power_button_pressed) { return CheckBatteryResult::Reboot; } } /* If we got to this point, we should show the low-battery charging screen. */ if (show_display && !is_showing_charging_icon) { boot::StartShowLowBatteryChargingIcon(); is_showing_charging_icon = true; } /* Wait a bit before checking again. */ constexpr auto BatteryChargeCheckInterval = TimeSpan::FromMilliSeconds(20); os::SleepThread(BatteryChargeCheckInterval); /* Update the charger. */ this->UpdateCharger(); } } } void CheckBatteryCharge() { /* Open a sessions for the charger/battery. */ boot::ChargerDriver charger_driver; boot::BatteryDriver battery_driver; /* Check if the battery is removed. */ { bool removed = false; if (R_FAILED(battery_driver.IsBatteryRemoved(std::addressof(removed))) || removed) { boot::ShutdownSystem(); } } /* Get the boot reason. */ const auto boot_reason = boot::GetBootReason(); /* Initialize the charger driver. */ if (R_FAILED(charger_driver.Initialize(boot_reason != spl::BootReason_RtcAlarm2))) /* Check that the charger input limit is greater than 150 milli-amps. */ { int input_current_limit_ma; if (R_FAILED(charger_driver.GetInputCurrentLimit(std::addressof(input_current_limit_ma)))) { boot::ShutdownSystem(); } if (input_current_limit_ma <= 150) { charger_driver.SetChargerConfiguration(powctl::ChargerConfiguration_ChargeDisable); boot::ShutdownSystem(); } } /* Get the charge parameters. */ const auto &charge_parameters = powctl::driver::impl::GetChargeParameters(); /* Get the charge voltage limit. */ int charge_voltage_limit_mv; if (boot_reason != spl::BootReason_RtcAlarm2 || charge_parameters.unknown_x_table == nullptr || charge_parameters.x_table_size == 0) { charge_voltage_limit_mv = charge_parameters.default_charge_voltage_limit; } else { if (R_FAILED(charger_driver.GetChargeVoltageLimit(std::addressof(charge_voltage_limit_mv)))) { boot::ShutdownSystem(); } } /* Create and update a battery checker. */ BatteryChecker battery_checker(charger_driver, battery_driver, charge_parameters, charge_voltage_limit_mv); battery_checker.UpdateCharger(); /* Set the display brightness to 25%. */ boot::SetDisplayBrightness(25); /* Check the battery. */ const CheckBatteryResult check_result = battery_checker.LoopCheckBattery(boot_reason); /* Set the display brightness to 100%. */ boot::SetDisplayBrightness(100); /* Handle the check result. */ switch (check_result) { case CheckBatteryResult::Success: break; case CheckBatteryResult::Shutdown: boot::ShutdownSystem(); break; case CheckBatteryResult::Reboot: boot::RebootSystem(); break; AMS_UNREACHABLE_DEFAULT_CASE(); } } }