From 7c36a827dad5ada2c647a126360e1c1d57ce7d52 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 6 May 2019 15:53:29 -0700 Subject: [PATCH] boot: implement bc24193 driver, part of CheckBatteryCharge --- .../boot/source/boot_battery_driver.cpp | 11 ++ .../boot/source/boot_battery_driver.hpp | 1 + .../boot/source/boot_bq24193_charger.hpp | 139 ++++++++++++++ .../boot/source/boot_charger_driver.cpp | 177 ++++++++++++++++++ .../boot/source/boot_charger_driver.hpp | 67 +++++++ .../boot/source/boot_check_battery.cpp | 72 +++++++ stratosphere/boot/source/boot_main.cpp | 2 +- 7 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 stratosphere/boot/source/boot_bq24193_charger.hpp create mode 100644 stratosphere/boot/source/boot_charger_driver.cpp create mode 100644 stratosphere/boot/source/boot_charger_driver.hpp create mode 100644 stratosphere/boot/source/boot_check_battery.cpp diff --git a/stratosphere/boot/source/boot_battery_driver.cpp b/stratosphere/boot/source/boot_battery_driver.cpp index 92008927f..8c9d607c5 100644 --- a/stratosphere/boot/source/boot_battery_driver.cpp +++ b/stratosphere/boot/source/boot_battery_driver.cpp @@ -296,3 +296,14 @@ Result BatteryDriver::InitializeBatteryParameters() { return ResultSuccess; } + +Result BatteryDriver::IsBatteryRemoved(bool *out) { + /* N doesn't check result, but we will. */ + u16 val = 0; + Result rc = this->Read(Max17050Status, &val); + if (R_FAILED(rc)) { + return rc; + } + *out = (val & 0x0008) == 0x0008; + return ResultSuccess; +} diff --git a/stratosphere/boot/source/boot_battery_driver.hpp b/stratosphere/boot/source/boot_battery_driver.hpp index 2ffbc1238..f12c81e46 100644 --- a/stratosphere/boot/source/boot_battery_driver.hpp +++ b/stratosphere/boot/source/boot_battery_driver.hpp @@ -53,4 +53,5 @@ class BatteryDriver { public: Result InitializeBatteryParameters(); + Result IsBatteryRemoved(bool *out); }; diff --git a/stratosphere/boot/source/boot_bq24193_charger.hpp b/stratosphere/boot/source/boot_bq24193_charger.hpp new file mode 100644 index 000000000..ad961e072 --- /dev/null +++ b/stratosphere/boot/source/boot_bq24193_charger.hpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018-2019 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 . + */ + +#pragma once +#include + +static constexpr u8 Bq24193InputSourceControl = 0x00; +static constexpr u8 Bq24193PowerOnConfiguration = 0x01; +static constexpr u8 Bq24193ChargeCurrentControl = 0x02; +static constexpr u8 Bq24193PreChargeTerminationCurrentControl = 0x03; +static constexpr u8 Bq24193ChargeVoltageControl = 0x04; +static constexpr u8 Bq24193ChargeTerminationTimerControl = 0x05; +static constexpr u8 Bq24193IrCompensationThermalRegulationControl = 0x06; +static constexpr u8 Bq24193MiscOperationControl = 0x07; +static constexpr u8 Bq24193SystemStatus = 0x08; +static constexpr u8 Bq24193Fault = 0x09; +static constexpr u8 Bq24193VendorPartRevisionStatus = 0x0A; + +enum ChargerConfiguration : u8 { + ChargerConfiguration_ChargeDisable = (0 << 4), + ChargerConfiguration_ChargeBattery = (1 << 4), + ChargerConfiguration_Otg = (2 << 4), +}; + +static constexpr u32 ChargeVoltageLimitMin = 3504; +static constexpr u32 ChargeVoltageLimitMax = 4208; + +static inline u8 EncodeChargeVoltageLimit(u32 voltage) { + if (voltage < ChargeVoltageLimitMin || voltage > ChargeVoltageLimitMax) { + std::abort(); + } + voltage -= ChargeVoltageLimitMin; + voltage >>= 4; + return static_cast(voltage << 2); +} + +static inline u32 DecodeChargeVoltageLimit(u8 reg) { + return ChargeVoltageLimitMin + (static_cast(reg & 0xFC) << 2); +} + +static constexpr u32 FastChargeCurrentLimitMin = 512; +static constexpr u32 FastChargeCurrentLimitMax = 4544; + +static inline u8 EncodeFastChargeCurrentLimit(u32 current) { + if (current < FastChargeCurrentLimitMin || current > FastChargeCurrentLimitMax) { + std::abort(); + } + current -= FastChargeCurrentLimitMin; + current >>= 6; + return static_cast(current << 2); +} + +static inline u32 DecodeFastChargeCurrentLimit(u8 reg) { + return FastChargeCurrentLimitMin + (static_cast(reg & 0xFC) << 4); +} + +enum InputCurrentLimit : u8 { + InputCurrentLimit_100mA = 0, + InputCurrentLimit_150mA = 1, + InputCurrentLimit_500mA = 2, + InputCurrentLimit_900mA = 3, + InputCurrentLimit_1200mA = 4, + InputCurrentLimit_1500mA = 5, + InputCurrentLimit_2000mA = 6, + InputCurrentLimit_3000mA = 7, +}; + +static constexpr u32 PreChargeCurrentLimitMin = 128; +static constexpr u32 PreChargeCurrentLimitMax = 2048; + +static inline u8 EncodePreChargeCurrentLimit(u32 current) { + if (current < PreChargeCurrentLimitMin || current > PreChargeCurrentLimitMax) { + std::abort(); + } + current -= PreChargeCurrentLimitMin; + current >>= 7; + return static_cast(current << 4); +} + +static inline u32 DecodePreChargeCurrentLimit(u8 reg) { + return PreChargeCurrentLimitMin + (static_cast(reg & 0xF0) << 3); +} + +static constexpr u32 TerminationCurrentLimitMin = 128; +static constexpr u32 TerminationCurrentLimitMax = 2048; + +static inline u8 EncodeTerminationCurrentLimit(u32 current) { + if (current < TerminationCurrentLimitMin || current > TerminationCurrentLimitMax) { + std::abort(); + } + current -= TerminationCurrentLimitMin; + current >>= 7; + return static_cast(current); +} + +static inline u32 DecodeTerminationCurrentLimit(u8 reg) { + return TerminationCurrentLimitMin + (static_cast(reg & 0xF) << 7); +} + +static constexpr u32 MinimumSystemVoltageLimitMin = 3000; +static constexpr u32 MinimumSystemVoltageLimitMax = 3700; + +static inline u8 EncodeMinimumSystemVoltageLimit(u32 voltage) { + if (voltage < MinimumSystemVoltageLimitMin || voltage > MinimumSystemVoltageLimitMax) { + std::abort(); + } + voltage -= MinimumSystemVoltageLimitMin; + voltage /= 100; + return static_cast(voltage << 1); +} + +static inline u32 DecodeMinimumSystemVoltageLimit(u8 reg) { + return MinimumSystemVoltageLimitMin + (static_cast(reg & 0x0E) * 50); +} + +enum WatchdogTimerSetting : u8 { + WatchdogTimerSetting_Disabled = (0 << 4), + WatchdogTimerSetting_40s = (1 << 4), + WatchdogTimerSetting_80s = (2 << 4), + WatchdogTimerSetting_160s = (3 << 4), +}; + +enum BoostModeCurrentLimit : u8 { + BoostModeCurrentLimit_500mA = 0, + BoostModeCurrentLimit_1300mA = 1, +}; \ No newline at end of file diff --git a/stratosphere/boot/source/boot_charger_driver.cpp b/stratosphere/boot/source/boot_charger_driver.cpp new file mode 100644 index 000000000..472ffbe5b --- /dev/null +++ b/stratosphere/boot/source/boot_charger_driver.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2018-2019 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 +#include "boot_charger_driver.hpp" + +Result ChargerDriver::Read(u8 addr, u8 *out) { + return Boot::ReadI2cRegister(this->i2c_session, reinterpret_cast(out), sizeof(*out), &addr, sizeof(addr)); +} + +Result ChargerDriver::Write(u8 addr, u8 val) { + return Boot::WriteI2cRegister(this->i2c_session, reinterpret_cast(&val), sizeof(val), &addr, sizeof(addr)); +} + +Result ChargerDriver::ReadWrite(u8 addr, u8 mask, u8 val) { + Result rc; + u8 cur_val; + if (R_FAILED((rc = this->Read(addr, &cur_val)))) { + return rc; + } + + const u8 new_val = (cur_val & ~mask) | val; + if (R_FAILED((rc = this->Write(addr, new_val)))) { + return rc; + } + return ResultSuccess; +} + +Result ChargerDriver::Initialize() { + return this->Initialize(true); +} + +Result ChargerDriver::Initialize(bool set_input_current_limit) { + Result rc; + if (set_input_current_limit) { + if (R_FAILED((rc = this->SetInputCurrentLimit(InputCurrentLimit_500mA)))) { + return rc; + } + } + + if (R_FAILED((rc = this->SetChargeVoltageLimit(4208)))) { + return rc; + } + + if (R_FAILED((rc = this->SetFastChargeCurrentLimit(512)))) { + return rc; + } + + if (R_FAILED((rc = this->SetForce20PercentChargeCurrent(false)))) { + return rc; + } + + if (R_FAILED((rc = this->SetPreChargeCurrentLimit(128)))) { + return rc; + } + + if (R_FAILED((rc = this->SetTerminationCurrentLimit(128)))) { + return rc; + } + + if (R_FAILED((rc = this->SetMinimumSystemVoltageLimit(3000)))) { + return rc; + } + + if (R_FAILED((rc = this->SetWatchdogTimerSetting(WatchdogTimerSetting_Disabled)))) { + return rc; + } + + if (R_FAILED((rc = this->SetChargingSafetyTimerEnabled(false)))) { + return rc; + } + + if (R_FAILED((rc = this->ResetWatchdogTimer()))) { + return rc; + } + + if (R_FAILED((rc = this->SetBoostModeCurrentLimit(BoostModeCurrentLimit_500mA)))) { + return rc; + } + + if (R_FAILED((rc = this->SetHiZEnabled(false)))) { + return rc; + } + + return ResultSuccess; +} + +Result ChargerDriver::SetChargeEnabled(bool enabled) { + Boot::GpioSetValue(GpioPadName_Bq24193Charger, enabled ? GpioValue_Low : GpioValue_High); + return this->SetChargerConfiguration(ChargerConfiguration_ChargeBattery); +} + +Result ChargerDriver::SetChargerConfiguration(ChargerConfiguration config) { + return this->ReadWrite(Bq24193PowerOnConfiguration, 0x30, config); +} + +Result ChargerDriver::SetChargeVoltageLimit(u32 voltage) { + return this->ReadWrite(Bq24193ChargeVoltageControl, 0xFC, EncodeChargeVoltageLimit(voltage)); +} + +Result ChargerDriver::SetFastChargeCurrentLimit(u32 current) { + return this->ReadWrite(Bq24193ChargeCurrentControl, 0xFC, EncodeFastChargeCurrentLimit(current)); +} + +Result ChargerDriver::SetInputCurrentLimit(InputCurrentLimit current) { + return this->ReadWrite(Bq24193InputSourceControl, 0x07, current); +} + +Result ChargerDriver::SetForce20PercentChargeCurrent(bool force) { + return this->ReadWrite(Bq24193ChargeCurrentControl, 0x01, force ? 1 : 0); +} + +Result ChargerDriver::SetPreChargeCurrentLimit(u32 current) { + return this->ReadWrite(Bq24193PreChargeTerminationCurrentControl, 0xF0, EncodePreChargeCurrentLimit(current)); +} + +Result ChargerDriver::SetTerminationCurrentLimit(u32 current) { + return this->ReadWrite(Bq24193PreChargeTerminationCurrentControl, 0x0F, EncodeTerminationCurrentLimit(current)); +} + +Result ChargerDriver::SetMinimumSystemVoltageLimit(u32 voltage) { + return this->ReadWrite(Bq24193PowerOnConfiguration, 0x0E, EncodeMinimumSystemVoltageLimit(voltage)); +} + +Result ChargerDriver::SetWatchdogTimerSetting(WatchdogTimerSetting setting) { + return this->ReadWrite(Bq24193ChargeTerminationTimerControl, 0x30, setting); +} + +Result ChargerDriver::SetChargingSafetyTimerEnabled(bool enabled) { + return this->ReadWrite(Bq24193ChargeTerminationTimerControl, 0x08, enabled ? 0x08 : 0); +} + +Result ChargerDriver::ResetWatchdogTimer() { + return this->ReadWrite(Bq24193PowerOnConfiguration, 0x40, 0x40); +} + +Result ChargerDriver::SetBoostModeCurrentLimit(BoostModeCurrentLimit current) { + return this->ReadWrite(Bq24193PowerOnConfiguration, 0x01, current); +} + +Result ChargerDriver::SetHiZEnabled(bool enabled) { + return this->ReadWrite(Bq24193InputSourceControl, 0x80, enabled ? 0x80 : 0); +} + +Result ChargerDriver::GetInputCurrentLimit(InputCurrentLimit *out) { + u8 limit; + Result rc = this->Read(Bq24193InputSourceControl, &limit); + if (R_FAILED(rc)) { + return rc; + } + *out = static_cast(limit); + return ResultSuccess; +} + +Result ChargerDriver::GetChargeVoltageLimit(u32 *out) { + u8 reg; + Result rc = this->Read(Bq24193ChargeVoltageControl, ®); + if (R_FAILED(rc)) { + return rc; + } + *out = DecodeChargeVoltageLimit(reg); + return ResultSuccess; +} diff --git a/stratosphere/boot/source/boot_charger_driver.hpp b/stratosphere/boot/source/boot_charger_driver.hpp new file mode 100644 index 000000000..01885b7b3 --- /dev/null +++ b/stratosphere/boot/source/boot_charger_driver.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2019 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 . + */ + +#pragma once +#include +#include + +#include "i2c_driver/i2c_api.hpp" +#include "boot_functions.hpp" +#include "boot_bq24193_charger.hpp" + +class ChargerDriver { + private: + static constexpr u32 GpioPadName_Bq24193Charger = 0xA; + private: + I2cSessionImpl i2c_session; + public: + ChargerDriver() { + I2cDriver::Initialize(); + I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Max17050); + + Boot::GpioSetDirection(GpioPadName_Bq24193Charger, GpioDirection_Output); + } + + ~ChargerDriver() { + I2cDriver::CloseSession(this->i2c_session); + I2cDriver::Finalize(); + } + private: + Result Read(u8 addr, u8 *out_data); + Result Write(u8 addr, u8 val); + Result ReadWrite(u8 addr, u8 mask, u8 val); + + Result SetInputCurrentLimit(InputCurrentLimit current); + Result SetForce20PercentChargeCurrent(bool force); + Result SetPreChargeCurrentLimit(u32 current); + Result SetTerminationCurrentLimit(u32 current); + Result SetMinimumSystemVoltageLimit(u32 voltage); + Result SetWatchdogTimerSetting(WatchdogTimerSetting setting); + Result SetChargingSafetyTimerEnabled(bool enabled); + Result ResetWatchdogTimer(); + Result SetBoostModeCurrentLimit(BoostModeCurrentLimit current); + Result SetHiZEnabled(bool enabled); + + public: + Result Initialize(); + Result Initialize(bool set_input_current_limit); + Result SetChargeVoltageLimit(u32 voltage); + Result SetFastChargeCurrentLimit(u32 current); + Result SetChargeEnabled(bool enabled); + Result SetChargerConfiguration(ChargerConfiguration config); + Result GetInputCurrentLimit(InputCurrentLimit *out); + Result GetChargeVoltageLimit(u32 *out); +}; diff --git a/stratosphere/boot/source/boot_check_battery.cpp b/stratosphere/boot/source/boot_check_battery.cpp new file mode 100644 index 000000000..4f966c596 --- /dev/null +++ b/stratosphere/boot/source/boot_check_battery.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018-2019 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 "boot_functions.hpp" +#include "boot_pmic_driver.hpp" +#include "boot_battery_driver.hpp" +#include "boot_charger_driver.hpp" + +enum CheckBatteryResult { + CheckBatteryResult_Success = 0, + CheckBatteryResult_Shutdown = 1, + CheckBatteryResult_Reboot = 2, +}; + +void Boot::CheckBatteryCharge() { + PmicDriver pmic_driver; + BatteryDriver battery_driver; + ChargerDriver charger_driver; + + if (R_FAILED(battery_driver.InitializeBatteryParameters())) { + pmic_driver.ShutdownSystem(); + } + { + bool removed; + if (R_FAILED(battery_driver.IsBatteryRemoved(&removed)) || removed) { + pmic_driver.ShutdownSystem(); + } + } + + const u32 boot_reason = Boot::GetBootReason(); + InputCurrentLimit input_current_limit; + if (R_FAILED(charger_driver.Initialize(boot_reason != 4)) || R_FAILED(charger_driver.GetInputCurrentLimit(&input_current_limit))) { + pmic_driver.ShutdownSystem(); + } + + if (input_current_limit <= InputCurrentLimit_150mA) { + charger_driver.SetChargerConfiguration(ChargerConfiguration_ChargeDisable); + pmic_driver.ShutdownSystem(); + } + + const u32 battery_version = Boot::GetBatteryVersion(); + + /* TODO: UpdateCharger(); */ + /* TODO: LoopCheckBattery(); */ + CheckBatteryResult check_result = CheckBatteryResult_Success; + + switch (check_result) { + case CheckBatteryResult_Success: + break; + case CheckBatteryResult_Shutdown: + pmic_driver.ShutdownSystem(); + break; + case CheckBatteryResult_Reboot: + pmic_driver.RebootSystem(); + break; + default: + std::abort(); + } +} diff --git a/stratosphere/boot/source/boot_main.cpp b/stratosphere/boot/source/boot_main.cpp index c1746f687..2517ac17d 100644 --- a/stratosphere/boot/source/boot_main.cpp +++ b/stratosphere/boot/source/boot_main.cpp @@ -124,7 +124,7 @@ int main(int argc, char **argv) Boot::ShowSplashScreen(); /* Check that the battery has enough to boot. */ - /* TODO: Boot::CheckBatteryCharge(); */ + Boot::CheckBatteryCharge(); /* Configure pinmux + drive pads. */ Boot::ConfigurePinmux();