diff --git a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_battery_driver.cpp b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_battery_driver.cpp
index 2e3b6dd00..e59436231 100644
--- a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_battery_driver.cpp
+++ b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_battery_driver.cpp
@@ -14,19 +14,72 @@
* along with this program. If not, see .
*/
#include
+#include "../../powctl_device_management.hpp"
+#include "powctl_retry_helper.hpp"
#include "powctl_battery_driver.hpp"
+#include "powctl_max17050_driver.hpp"
namespace ams::powctl::impl::board::nintendo_nx {
+ namespace {
+
+ constinit std::optional g_battery_device;
+
+ Max17050Driver &GetMax17050Driver() {
+ static Max17050Driver s_max17050_driver;
+ return s_max17050_driver;
+ }
+
+ }
+
/* Generic API. */
void BatteryDriver::InitializeDriver() {
- /* TODO */
- AMS_ABORT();
+ /* Initialize gpio library. */
+ gpio::Initialize();
+
+ /* Create battery device. */
+ g_battery_device.emplace(this->IsEventHandlerEnabled());
+
+ /* Initialize the Max17050Driver. */
+ {
+ size_t battery_vendor_size;
+ char battery_vendor[0x18] = {};
+ if (R_FAILED(cal::GetBatteryVendor(std::addressof(battery_vendor_size), battery_vendor, sizeof(battery_vendor)))) {
+ battery_vendor[7] = 'A';
+ battery_vendor_size = 0;
+ }
+
+ u8 battery_version = 0;
+ if (R_FAILED(cal::GetBatteryVersion(std::addressof(battery_version)))) {
+ battery_version = 0;
+ }
+
+ GetMax17050Driver().Initialize(battery_vendor, battery_version);
+ }
+
+ /* Register our device. */
+ this->RegisterDevice(std::addressof(*g_battery_device));
+
+ /* Register the charger device's code. */
+ R_ABORT_UNLESS(powctl::impl::RegisterDeviceCode(powctl::DeviceCode_Max17050, std::addressof(*g_battery_device)));
+
}
void BatteryDriver::FinalizeDriver() {
- /* TODO */
- AMS_ABORT();
+ /* Unregister the charger device code. */
+ powctl::impl::UnregisterDeviceCode(powctl::DeviceCode_Max17050);
+
+ /* Unregister our device. */
+ this->UnregisterDevice(std::addressof(*g_battery_device));
+
+ /* Finalize Max17050Driver. */
+ GetMax17050Driver().Finalize();
+
+ /* Destroy the charger device. */
+ g_battery_device = std::nullopt;
+
+ /* Finalize gpio library. */
+ gpio::Finalize();
}
Result BatteryDriver::GetDeviceSystemEvent(os::SystemEventType **out, IDevice *device) {
diff --git a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_bq24193_driver.hpp b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_bq24193_driver.hpp
index 5c20aebe6..01d9b906c 100644
--- a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_bq24193_driver.hpp
+++ b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_bq24193_driver.hpp
@@ -50,10 +50,28 @@ namespace ams::powctl::impl::board::nintendo_nx {
void Initialize() {
std::scoped_lock lk(this->mutex);
if ((this->init_count++) == 0) {
+ /* Initialize i2c library. */
+ i2c::InitializeEmpty();
+
+ /* Open session. */
+ R_ABORT_UNLESS(i2c::OpenSession(std::addressof(this->i2c_session), i2c::DeviceCode_Bq24193));
+
+ /* Initialize session. */
R_ABORT_UNLESS(this->InitializeSession());
}
}
+ void Finalize() {
+ std::scoped_lock lk(this->mutex);
+ if ((--this->init_count) == 0) {
+ /* Close session. */
+ i2c::CloseSession(this->i2c_session);
+
+ /* Finalize i2c library. */
+ i2c::Finalize();
+ }
+ }
+
Result SetPreChargeCurrentLimit(int ma);
Result SetTerminationCurrentLimit(int ma);
diff --git a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_charger_driver.cpp b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_charger_driver.cpp
index 753f41a2e..3771bbb44 100644
--- a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_charger_driver.cpp
+++ b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_charger_driver.cpp
@@ -81,6 +81,12 @@ namespace ams::powctl::impl::board::nintendo_nx {
/* Destroy the charger device. */
g_charger_device = std::nullopt;
+
+ /* Finalize gpio library. */
+ gpio::Finalize();
+
+ /* Finalize Bq24193Driver. */
+ GetBq24193Driver().Finalize();
}
Result ChargerDriver::GetDeviceSystemEvent(os::SystemEventType **out, IDevice *device) {
diff --git a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_custom_parameters.inc b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_custom_parameters.inc
new file mode 100644
index 000000000..03c5a02f5
--- /dev/null
+++ b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_custom_parameters.inc
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2018-2020 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 .
+ */
+
+/* NOTE: This file is auto-generated by max17050_parameters_gen.py, do not edit manually. */
+
+constexpr inline const CustomParameters CustomParameters0A = {
+ .relaxcfg = 0x203B,
+ .rcomp0 = 0x0053,
+ .tempco = 0x1C22,
+ .ichgterm = 0x0333,
+ .tgain = 0xE1F6,
+ .toff = 0x2BF2,
+ .vempty = 0xA05F,
+ .qresidual00 = 0x5786,
+ .qresidual10 = 0x3184,
+ .qresidual20 = 0x1E00,
+ .qresidual30 = 0x1502,
+ .fullcap = 0x2476,
+ .vffullcap = 0x2476,
+ .modeltbl = {
+ 0x9FF0, 0xAD30, 0xB5D0, 0xB9C0, 0xBAD0, 0xBBE0, 0xBC30, 0xBC90,
+ 0xBCE0, 0xBD40, 0xBE70, 0xC0E0, 0xC4E0, 0xC890, 0xCC90, 0xD0F0,
+ 0x0170, 0x0480, 0x0590, 0x0BE0, 0x0A00, 0x3C00, 0x3810, 0x3A00,
+ 0x3A30, 0x19F0, 0x0EF0, 0x0AF0, 0x0BD0, 0x07F0, 0x06F0, 0x06F0,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ },
+ .fullsocthr = 0x5F00,
+ .iavgempty = 0x1D2A,
+};
+
+constexpr inline const CustomParameters CustomParameters0R = {
+ .relaxcfg = 0x203B,
+ .rcomp0 = 0x0048,
+ .tempco = 0x2034,
+ .ichgterm = 0x0333,
+ .tgain = 0xE1F6,
+ .toff = 0x2BF2,
+ .vempty = 0xA05F,
+ .qresidual00 = 0x5A00,
+ .qresidual10 = 0x3B00,
+ .qresidual20 = 0x0F80,
+ .qresidual30 = 0x0B02,
+ .fullcap = 0x2466,
+ .vffullcap = 0x2466,
+ .modeltbl = {
+ 0x9C50, 0xAD90, 0xB270, 0xB6A0, 0xB8F0, 0xBB10, 0xBC00, 0xBD00,
+ 0xBD70, 0xBE70, 0xBF50, 0xC1F0, 0xC380, 0xC590, 0xC8E0, 0xD0B0,
+ 0x00D0, 0x0150, 0x0300, 0x0D00, 0x0E00, 0x1900, 0x2AC0, 0x2830,
+ 0x1760, 0x18F0, 0x0DF0, 0x0BC0, 0x0DF0, 0x0BF0, 0x06F0, 0x06F0,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ },
+ .fullsocthr = 0x5F00,
+ .iavgempty = 0x1D2A,
+};
+
+constexpr inline const CustomParameters CustomParameters0M = {
+ .relaxcfg = 0x203B,
+ .rcomp0 = 0x0085,
+ .tempco = 0x1625,
+ .ichgterm = 0x0333,
+ .tgain = 0xE1F6,
+ .toff = 0x2BF2,
+ .vempty = 0xA05F,
+ .qresidual00 = 0x3100,
+ .qresidual10 = 0x1B00,
+ .qresidual20 = 0x1000,
+ .qresidual30 = 0x0C81,
+ .fullcap = 0x227A,
+ .vffullcap = 0x227A,
+ .modeltbl = {
+ 0xA340, 0xB840, 0xB900, 0xBB70, 0xBC90, 0xBD20, 0xBDC0, 0xBEA0,
+ 0xBF70, 0xC030, 0xC210, 0xC3F0, 0xC800, 0xC9E0, 0xCCA0, 0xD090,
+ 0x0160, 0x3800, 0x0800, 0x1E00, 0x2550, 0x3060, 0x15D0, 0x1810,
+ 0x1490, 0x0B80, 0x0BF0, 0x0AF0, 0x0CB0, 0x06F0, 0x09D0, 0x09D0,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+ },
+ .fullsocthr = 0x5F00,
+ .iavgempty = 0x1D2A,
+};
+
+constexpr inline const CustomParameters CustomParameters1 = {
+ .relaxcfg = 0x203B,
+ .rcomp0 = 0x0040,
+ .tempco = 0x1624,
+ .ichgterm = 0x0333,
+ .tgain = 0xE1F6,
+ .toff = 0x2BF2,
+ .vempty = 0xA05F,
+ .qresidual00 = 0x4690,
+ .qresidual10 = 0x2605,
+ .qresidual20 = 0x1605,
+ .qresidual30 = 0x0F05,
+ .fullcap = 0x1AE4,
+ .vffullcap = 0x1AE4,
+ .modeltbl = {
+ 0x8B50, 0x9C20, 0xACF0, 0xB160, 0xB3A0, 0xB5B0, 0xB950, 0xBBE0,
+ 0xBDC0, 0xBEF0, 0xC140, 0xC250, 0xC600, 0xC960, 0xCCE0, 0xD060,
+ 0x0070, 0x00F0, 0x0440, 0x0400, 0x0500, 0x0400, 0x0D00, 0x3270,
+ 0x0FB0, 0x0AF0, 0x10F0, 0x0CE0, 0x09E0, 0x07F0, 0x06F0, 0x06F0,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ },
+ .fullsocthr = 0x5F00,
+ .iavgempty = 0x1584,
+};
+
+constexpr inline const CustomParameters CustomParameters2A = {
+ .relaxcfg = 0x203B,
+ .rcomp0 = 0x004A,
+ .tempco = 0x1D23,
+ .ichgterm = 0x0333,
+ .tgain = 0xE1F6,
+ .toff = 0x2BF2,
+ .vempty = 0xA05F,
+ .qresidual00 = 0x4000,
+ .qresidual10 = 0x1E80,
+ .qresidual20 = 0x0D83,
+ .qresidual30 = 0x0783,
+ .fullcap = 0x1C20,
+ .vffullcap = 0x1C20,
+ .modeltbl = {
+ 0x8040, 0x9A30, 0xB430, 0xB770, 0xBAB0, 0xBBC0, 0xBD00, 0xBE50,
+ 0xBF70, 0xC0D0, 0xC300, 0xC590, 0xC960, 0xCD40, 0xD1F0, 0xD5C0,
+ 0x0040, 0x0060, 0x0510, 0x0D30, 0x16C0, 0x2160, 0x1380, 0x1A10,
+ 0x0EC0, 0x0CE0, 0x08F0, 0x0940, 0x0920, 0x06F0, 0x06C0, 0x06C0,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ },
+ .fullsocthr = 0x5500,
+ .iavgempty = 0x1680,
+};
+
+constexpr inline const CustomParameters CustomParameters2R = {
+ .relaxcfg = 0x203B,
+ .rcomp0 = 0x004C,
+ .tempco = 0x2D32,
+ .ichgterm = 0x0333,
+ .tgain = 0xE1F6,
+ .toff = 0x2BF2,
+ .vempty = 0xA05F,
+ .qresidual00 = 0x5900,
+ .qresidual10 = 0x2900,
+ .qresidual20 = 0x1100,
+ .qresidual30 = 0x0B00,
+ .fullcap = 0x1CCE,
+ .vffullcap = 0x1CCE,
+ .modeltbl = {
+ 0x8E10, 0x9FC0, 0xA880, 0xB750, 0xBA10, 0xBB30, 0xBD20, 0xBE80,
+ 0xC0A0, 0xC350, 0xC670, 0xC8C0, 0xCCF0, 0xD050, 0xD140, 0xD5F0,
+ 0x0020, 0x00D0, 0x0200, 0x0E00, 0x1300, 0x1B00, 0x1930, 0x1150,
+ 0x0BF0, 0x07E0, 0x0AD0, 0x06F0, 0x07F0, 0x0EF0, 0x04F0, 0x04F0,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ },
+ .fullsocthr = 0x5500,
+ .iavgempty = 0x170B,
+};
+
+constexpr inline const CustomParameters CustomParameters2M = {
+ .relaxcfg = 0x203B,
+ .rcomp0 = 0x0049,
+ .tempco = 0x222A,
+ .ichgterm = 0x0333,
+ .tgain = 0xE1F6,
+ .toff = 0x2BF2,
+ .vempty = 0xA05F,
+ .qresidual00 = 0x4F00,
+ .qresidual10 = 0x2680,
+ .qresidual20 = 0x1205,
+ .qresidual30 = 0x0C87,
+ .fullcap = 0x1C68,
+ .vffullcap = 0x1C68,
+ .modeltbl = {
+ 0x8E40, 0xB570, 0xB8F0, 0xBB00, 0xBC20, 0xBCC0, 0xBE30, 0xBFE0,
+ 0xC200, 0xC400, 0xC720, 0xCB50, 0xCF00, 0xD100, 0xD480, 0xD5C0,
+ 0x00C0, 0x0C00, 0x0A10, 0x1800, 0x2C00, 0x1C10, 0x12D0, 0x09F0,
+ 0x0AF0, 0x0850, 0x09F0, 0x06F0, 0x06B0, 0x07E0, 0x01D0, 0x01D0,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200,
+ },
+ .fullsocthr = 0x5500,
+ .iavgempty = 0x16B9,
+};
+
diff --git a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_driver.cpp b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_driver.cpp
new file mode 100644
index 000000000..61e049084
--- /dev/null
+++ b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_driver.cpp
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) 2018-2020 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 "powctl_max17050_driver.hpp"
+
+namespace ams::powctl::impl::board::nintendo_nx {
+
+ namespace max17050 {
+
+ constexpr inline u8 Status = 0x00;
+ constexpr inline u8 VAlrtThreshold = 0x01;
+ constexpr inline u8 TAlrtThreshold = 0x02;
+ constexpr inline u8 SocAlrtThreshold = 0x03;
+ constexpr inline u8 AtRate = 0x04;
+ constexpr inline u8 RemCapRep = 0x05;
+ constexpr inline u8 SocRep = 0x06;
+ constexpr inline u8 Age = 0x07;
+ constexpr inline u8 Temperature = 0x08;
+ constexpr inline u8 VCell = 0x09;
+ constexpr inline u8 Current = 0x0A;
+ constexpr inline u8 AverageCurrent = 0x0B;
+
+ constexpr inline u8 SocMix = 0x0D;
+ constexpr inline u8 SocAv = 0x0E;
+ constexpr inline u8 RemCapMix = 0x0F;
+ constexpr inline u8 FullCap = 0x10;
+ constexpr inline u8 Tte = 0x11;
+ constexpr inline u8 QResidual00 = 0x12;
+ constexpr inline u8 FullSocThr = 0x13;
+
+
+ constexpr inline u8 AverageTemp = 0x16;
+ constexpr inline u8 Cycles = 0x17;
+ constexpr inline u8 DesignCap = 0x18;
+ constexpr inline u8 AverageVCell = 0x19;
+ constexpr inline u8 MaxMinTemp = 0x1A;
+ constexpr inline u8 MaxMinVoltage = 0x1B;
+ constexpr inline u8 MaxMinCurrent = 0x1C;
+ constexpr inline u8 Config = 0x1D;
+ constexpr inline u8 IChgTerm = 0x1E;
+ constexpr inline u8 RemCapAv = 0x1F;
+
+ constexpr inline u8 Version = 0x21;
+ constexpr inline u8 QResidual10 = 0x22;
+ constexpr inline u8 FullCapNom = 0x23;
+ constexpr inline u8 TempNom = 0x24;
+ constexpr inline u8 TempLim = 0x25;
+
+ constexpr inline u8 Ain = 0x27;
+ constexpr inline u8 LearnCfg = 0x28;
+ constexpr inline u8 FilterCfg = 0x29;
+ constexpr inline u8 RelaxCfg = 0x2A;
+ constexpr inline u8 MiscCfg = 0x2B;
+ constexpr inline u8 TGain = 0x2C;
+ constexpr inline u8 TOff = 0x2D;
+ constexpr inline u8 CGain = 0x2E;
+ constexpr inline u8 COff = 0x2F;
+
+
+ constexpr inline u8 QResidual20 = 0x32;
+
+
+
+ constexpr inline u8 IAvgEmpty = 0x36;
+ constexpr inline u8 FCtc = 0x37;
+ constexpr inline u8 RComp0 = 0x38;
+ constexpr inline u8 TempCo = 0x39;
+ constexpr inline u8 VEmpty = 0x3A;
+
+
+ constexpr inline u8 FStat = 0x3D;
+ constexpr inline u8 Timer = 0x3E;
+ constexpr inline u8 ShdnTimer = 0x3F;
+
+
+ constexpr inline u8 QResidual30 = 0x42;
+
+
+ constexpr inline u8 DQAcc = 0x45;
+ constexpr inline u8 DPAcc = 0x46;
+
+ constexpr inline u8 SocVf0 = 0x48;
+
+ constexpr inline u8 Qh0 = 0x4C;
+ constexpr inline u8 Qh = 0x4D;
+
+ constexpr inline u8 SocVfAccess = 0x60;
+
+ constexpr inline u8 ModelAccess0 = 0x62;
+ constexpr inline u8 ModelAccess1 = 0x63;
+
+ constexpr inline u8 ModelChrTblStart = 0x80;
+ constexpr inline u8 ModelChrTblEnd = 0xB0;
+
+
+ constexpr inline u8 VFocV = 0xFB;
+ constexpr inline u8 SocVf = 0xFF;
+
+ constexpr inline size_t ModelChrTblSize = ModelChrTblEnd - ModelChrTblStart;
+
+ namespace {
+
+ struct CustomParameters {
+ u16 relaxcfg;
+ u16 rcomp0;
+ u16 tempco;
+ u16 ichgterm;
+ u16 tgain;
+ u16 toff;
+ u16 vempty;
+ u16 qresidual00;
+ u16 qresidual10;
+ u16 qresidual20;
+ u16 qresidual30;
+ u16 fullcap;
+ u16 vffullcap;
+ u16 modeltbl[ModelChrTblSize];
+ u16 fullsocthr;
+ u16 iavgempty;
+ };
+
+ #include "powctl_max17050_custom_parameters.inc"
+
+ const CustomParameters &GetCustomParameters(const char *battery_vendor, u8 battery_version) {
+ if (battery_version == 2) {
+ if (battery_vendor[7] == 'M') {
+ return CustomParameters2M;
+ } else if (battery_vendor[7] == 'R') {
+ return CustomParameters2R;
+ } else /* if (battery_vendor[7] == 'A') */ {
+ return CustomParameters2A;
+ }
+ } else if (battery_version == 1) {
+ return CustomParameters1;
+ } else /* if (battery_version == 0) */ {
+ if (battery_vendor[7] == 'M') {
+ return CustomParameters0M;
+ } else if (battery_vendor[7] == 'R') {
+ return CustomParameters0R;
+ } else /* if (battery_vendor[7] == 'A') */ {
+ return CustomParameters0A;
+ }
+ }
+ }
+
+ }
+
+ }
+
+ namespace {
+
+ ALWAYS_INLINE Result ReadWriteRegister(const i2c::I2cSession &session, u8 address, u16 mask, u16 value) {
+ /* Read the current value. */
+ u16 cur_val;
+ R_TRY(i2c::ReadSingleRegister(session, address, std::addressof(cur_val)));
+
+ /* Update the value. */
+ const u16 new_val = (cur_val & ~mask) | (value & mask);
+ R_TRY(i2c::WriteSingleRegister(session, address, new_val));
+
+ return ResultSuccess();
+ }
+
+ ALWAYS_INLINE Result ReadRegister(const i2c::I2cSession &session, u8 address, u16 *out) {
+ return i2c::ReadSingleRegister(session, address, out);
+ }
+
+ ALWAYS_INLINE Result WriteRegister(const i2c::I2cSession &session, u8 address, u16 val) {
+ return i2c::WriteSingleRegister(session, address, val);
+ }
+
+ ALWAYS_INLINE bool WriteValidateRegister(const i2c::I2cSession &session, u8 address, u16 val) {
+ /* Write the value. */
+ R_ABORT_UNLESS(WriteRegister(session, address, val));
+
+ /* Give it time to take. */
+ os::SleepThread(TimeSpan::FromMilliSeconds(3));
+
+ /* Read it back. */
+ u16 new_val;
+ R_ABORT_UNLESS(ReadRegister(session, address, std::addressof(new_val)));
+
+ return new_val == val;
+ }
+
+ ALWAYS_INLINE Result ReadWriteValidateRegister(const i2c::I2cSession &session, u8 address, u16 mask, u16 value) {
+ /* Read the current value. */
+ u16 cur_val;
+ R_TRY(i2c::ReadSingleRegister(session, address, std::addressof(cur_val)));
+
+ /* Update the value. */
+ const u16 new_val = (cur_val & ~mask) | (value & mask);
+ while (!WriteValidateRegister(session, address, new_val)) { /* ... */ }
+
+ return ResultSuccess();
+ }
+
+ }
+
+ Result Max17050Driver::InitializeSession(const char *battery_vendor, u8 battery_version) {
+ /* Get the custom parameters. */
+ const auto ¶ms = max17050::GetCustomParameters(battery_vendor, battery_version);
+
+ /* We only want to write the parameters on power on reset. */
+ R_SUCCEED_IF(!this->IsPowerOnReset());
+
+ /* Set that we need to restore parameters. */
+ R_TRY(this->SetNeedToRestoreParameters(true));
+
+ /* Wait for our configuration to take. */
+ os::SleepThread(TimeSpan::FromMilliSeconds(500));
+
+ /* Write initial config. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::Config, 0x7210));
+
+ /* Write initial filter config. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::FilterCfg, 0x8784));
+
+ /* Write relax config. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::RelaxCfg, params.relaxcfg));
+
+ /* Write initial learn config. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::LearnCfg, 0x2603));
+
+ /* Write fullsocthr. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::FullSocThr, params.fullsocthr));
+
+ /* Write iavgempty. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::IAvgEmpty, params.iavgempty));
+
+ /* Unlock model table, write model table. */
+ do {
+ R_TRY(this->UnlockModelTable());
+ R_TRY(this->SetModelTable(params.modeltbl));
+ } while (!this->IsModelTableSet(params.modeltbl));
+
+ /* Lock the model table, trying up to ten times. */
+ {
+ size_t i = 0;
+ while (true) {
+ ++i;
+
+ R_TRY(this->LockModelTable());
+
+ if (this->IsModelTableLocked()) {
+ break;
+ }
+
+ R_SUCCEED_IF(i >= 10);
+ }
+ }
+
+ /* Write and validate rcomp0 */
+ while (!WriteValidateRegister(this->i2c_session, max17050::RComp0, params.rcomp0)) { /* ... */ }
+
+ /* Write and validate tempco */
+ while (!WriteValidateRegister(this->i2c_session, max17050::TempCo, params.tempco)) { /* ... */ }
+
+ /* Write ichgterm. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::IChgTerm, params.ichgterm));
+
+ /* Write tgain. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::TGain, params.tgain));
+
+ /* Write toff. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::TOff, params.toff));
+
+ /* Write and validate vempty. */
+ while (!WriteValidateRegister(this->i2c_session, max17050::VEmpty, params.vempty)) { /* ... */ }
+
+ /* Write and validate qresidual. */
+ while (!WriteValidateRegister(this->i2c_session, max17050::QResidual00, params.qresidual00)) { /* ... */ }
+ while (!WriteValidateRegister(this->i2c_session, max17050::QResidual10, params.qresidual10)) { /* ... */ }
+ while (!WriteValidateRegister(this->i2c_session, max17050::QResidual20, params.qresidual20)) { /* ... */ }
+ while (!WriteValidateRegister(this->i2c_session, max17050::QResidual30, params.qresidual30)) { /* ... */ }
+
+ /* Write capacity parameters. */
+ while (!WriteValidateRegister(this->i2c_session, max17050::FullCap, params.fullcap)) { /* ... */ }
+ R_TRY(WriteRegister(this->i2c_session, max17050::DesignCap, params.vffullcap));
+ while (!WriteValidateRegister(this->i2c_session, max17050::FullCapNom, params.vffullcap)) { /* ... */ }
+
+ /* Give some time for configuration to take. */
+ os::SleepThread(TimeSpan::FromMilliSeconds(350));
+
+ /* Write vfsoc to vfsoc0, qh, to qh0. */
+ u16 vfsoc, qh;
+ {
+ R_TRY(ReadRegister(this->i2c_session, max17050::SocVf, std::addressof(vfsoc)));
+ R_TRY(this->UnlockVfSoc());
+ while (!WriteValidateRegister(this->i2c_session, max17050::SocVf0, vfsoc)) { /* ... */ }
+ R_TRY(ReadRegister(this->i2c_session, max17050::Qh, std::addressof(qh)));
+ R_TRY(WriteRegister(this->i2c_session, max17050::Qh0, qh));
+ R_TRY(this->LockVfSoc());
+ }
+
+ /* Reset cycles. */
+ while (!WriteValidateRegister(this->i2c_session, max17050::Cycles, 0x0060)) { /* ... */ }
+
+ /* Load new capacity parameters. */
+ const u16 remcap = static_cast((vfsoc * params.vffullcap) / 0x6400);
+ const u16 repcap = static_cast(remcap * (params.fullcap / params.vffullcap));
+ const u16 dpacc = 0x0C80;
+ const u16 dqacc = params.vffullcap / 0x10;
+ while (!WriteValidateRegister(this->i2c_session, max17050::RemCapMix, remcap)) { /* ... */ }
+ while (!WriteValidateRegister(this->i2c_session, max17050::RemCapRep, repcap)) { /* ... */ }
+ while (!WriteValidateRegister(this->i2c_session, max17050::DPAcc, dpacc)) { /* ... */ }
+ while (!WriteValidateRegister(this->i2c_session, max17050::DQAcc, dqacc)) { /* ... */ }
+
+ /* Write capacity parameters. */
+ while (!WriteValidateRegister(this->i2c_session, max17050::FullCap, params.fullcap)) { /* ... */ }
+ R_TRY(WriteRegister(this->i2c_session, max17050::DesignCap, params.vffullcap));
+ while (!WriteValidateRegister(this->i2c_session, max17050::FullCapNom, params.vffullcap)) { /* ... */ }
+
+ /* Write soc rep. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::SocRep, vfsoc));
+
+ /* Clear power on reset. */
+ R_TRY(ReadWriteValidateRegister(this->i2c_session, max17050::Status, 0x0002, 0x0000));
+
+ /* Set cgain. */
+ R_TRY(WriteRegister(this->i2c_session, max17050::CGain, 0x7FFF));
+
+ return ResultSuccess();
+ }
+
+ Result Max17050Driver::SetMaximumShutdownTimerThreshold() {
+ return WriteRegister(this->i2c_session, max17050::ShdnTimer, 0xE000);
+ }
+
+ bool Max17050Driver::IsPowerOnReset() {
+ /* Get the register. */
+ u16 val;
+ R_ABORT_UNLESS(ReadRegister(this->i2c_session, max17050::Status, std::addressof(val)));
+
+ /* Extract the value. */
+ return (val & 0x0002) != 0;
+ }
+
+ Result Max17050Driver::LockVfSoc() {
+ return WriteRegister(this->i2c_session, max17050::SocVfAccess, 0x0000);
+ }
+
+ Result Max17050Driver::UnlockVfSoc() {
+ return WriteRegister(this->i2c_session, max17050::SocVfAccess, 0x0080);
+ }
+
+ Result Max17050Driver::LockModelTable() {
+ R_TRY(WriteRegister(this->i2c_session, max17050::ModelAccess0, 0x0000));
+ R_TRY(WriteRegister(this->i2c_session, max17050::ModelAccess1, 0x0000));
+ return ResultSuccess();
+ }
+
+ Result Max17050Driver::UnlockModelTable() {
+ R_TRY(WriteRegister(this->i2c_session, max17050::ModelAccess0, 0x0059));
+ R_TRY(WriteRegister(this->i2c_session, max17050::ModelAccess1, 0x00C4));
+ return ResultSuccess();
+ }
+
+ bool Max17050Driver::IsModelTableLocked() {
+ for (size_t i = 0; i < max17050::ModelChrTblSize; ++i) {
+ u16 val;
+ R_ABORT_UNLESS(ReadRegister(this->i2c_session, max17050::ModelChrTblStart + i, std::addressof(val)));
+
+ if (val != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ Result Max17050Driver::SetModelTable(const u16 *model_table) {
+ for (size_t i = 0; i < max17050::ModelChrTblSize; ++i) {
+ R_TRY(WriteRegister(this->i2c_session, max17050::ModelChrTblStart + i, model_table[i]));
+ }
+
+ return ResultSuccess();
+ }
+
+ bool Max17050Driver::IsModelTableSet(const u16 *model_table) {
+ for (size_t i = 0; i < max17050::ModelChrTblSize; ++i) {
+ u16 val;
+ R_ABORT_UNLESS(ReadRegister(this->i2c_session, max17050::ModelChrTblStart + i, std::addressof(val)));
+
+ if (val != model_table[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ Result Max17050Driver::GetNeedToRestoreParameters(bool *out) {
+ /* Get the register. */
+ u16 val;
+ R_TRY(ReadRegister(this->i2c_session, max17050::MiscCfg, std::addressof(val)));
+
+ /* Extract the value. */
+ *out = (val & 0x8000) != 0;
+ return ResultSuccess();
+ }
+
+ Result Max17050Driver::SetNeedToRestoreParameters(bool en) {
+ return ReadWriteRegister(this->i2c_session, max17050::MiscCfg, 0x8000, en ? 0x8000 : 0);
+ }
+
+}
diff --git a/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_driver.hpp b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_driver.hpp
new file mode 100644
index 000000000..0cdbfbd51
--- /dev/null
+++ b/libraries/libstratosphere/source/powctl/impl/board/nintendo_nx/powctl_max17050_driver.hpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2018-2020 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
+
+namespace ams::powctl::impl::board::nintendo_nx {
+
+ namespace max17050 {
+
+ }
+
+ class Max17050Driver {
+ private:
+ os::SdkMutex mutex;
+ int init_count;
+ i2c::I2cSession i2c_session;
+ private:
+ Result InitializeSession(const char *battery_vendor, u8 battery_version);
+ Result SetMaximumShutdownTimerThreshold();
+
+ bool IsPowerOnReset();
+ Result LockVfSoc();
+ Result UnlockVfSoc();
+ Result LockModelTable();
+ Result UnlockModelTable();
+ bool IsModelTableLocked();
+ Result SetModelTable(const u16 *model_table);
+ bool IsModelTableSet(const u16 *model_table);
+ public:
+ Max17050Driver() : mutex(), init_count(0), i2c_session() {
+ /* ... */
+ }
+
+ void Initialize(const char *battery_vendor, u8 battery_version) {
+ std::scoped_lock lk(this->mutex);
+ if ((this->init_count++) == 0) {
+ /* Initialize i2c library. */
+ i2c::InitializeEmpty();
+
+ /* Open session. */
+ R_ABORT_UNLESS(i2c::OpenSession(std::addressof(this->i2c_session), i2c::DeviceCode_Max17050));
+
+ /* Initialize session. */
+ R_ABORT_UNLESS(this->InitializeSession(battery_vendor, battery_version));
+
+ /* Set shutdown timer threshold to the maximum value. */
+ R_ABORT_UNLESS(this->SetMaximumShutdownTimerThreshold());
+ }
+ }
+
+ void Finalize() {
+ std::scoped_lock lk(this->mutex);
+ if ((--this->init_count) == 0) {
+ /* Close session. */
+ i2c::CloseSession(this->i2c_session);
+
+ /* Finalize i2c library. */
+ i2c::Finalize();
+ }
+ }
+
+ Result GetNeedToRestoreParameters(bool *out);
+ Result SetNeedToRestoreParameters(bool en);
+ };
+
+}