2019-05-06 16:00:22 +01:00
|
|
|
/*
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "boot_battery_driver.hpp"
|
2019-06-22 08:10:21 +01:00
|
|
|
#include "boot_calibration.hpp"
|
|
|
|
#include "boot_i2c_utils.hpp"
|
|
|
|
|
2019-10-24 10:30:10 +01:00
|
|
|
namespace ams::boot {
|
2019-06-22 08:10:21 +01:00
|
|
|
|
|
|
|
/* Include configuration into anonymous namespace. */
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
#include "boot_battery_parameters.inc"
|
|
|
|
|
|
|
|
const Max17050Parameters *GetBatteryParameters() {
|
|
|
|
const u32 battery_version = GetBatteryVersion();
|
|
|
|
const u32 battery_vendor = GetBatteryVendor();
|
|
|
|
|
|
|
|
if (battery_version == 2) {
|
|
|
|
if (battery_vendor == 'M') {
|
|
|
|
return &Max17050Params2M;
|
|
|
|
} else {
|
|
|
|
return &Max17050Params2;
|
|
|
|
}
|
|
|
|
} else if (battery_version == 1) {
|
|
|
|
return &Max17050Params1;
|
|
|
|
} else {
|
|
|
|
switch (battery_vendor) {
|
|
|
|
case 'M':
|
|
|
|
return &Max17050ParamsM;
|
|
|
|
case 'R':
|
|
|
|
return &Max17050ParamsR;
|
|
|
|
case 'A':
|
|
|
|
default:
|
|
|
|
return &Max17050ParamsA;
|
|
|
|
}
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
}
|
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::Read(u8 addr, u16 *out) {
|
|
|
|
return ReadI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(out), sizeof(*out), &addr, sizeof(addr));
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::Write(u8 addr, u16 val) {
|
|
|
|
return WriteI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(&val), sizeof(val), &addr, sizeof(addr));
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::ReadWrite(u8 addr, u16 mask, u16 val) {
|
|
|
|
u16 cur_val;
|
|
|
|
R_TRY(this->Read(addr, &cur_val));
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
const u16 new_val = (cur_val & ~mask) | val;
|
|
|
|
R_TRY(this->Write(addr, new_val));
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
bool BatteryDriver::WriteValidate(u8 addr, u16 val) {
|
|
|
|
/* Nintendo doesn't seem to check errors when doing this? */
|
|
|
|
/* It's probably okay, since the value does get validated. */
|
|
|
|
/* That said, we will validate the read to avoid uninit data problems. */
|
|
|
|
this->Write(addr, val);
|
|
|
|
svcSleepThread(3'000'000ul);
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
u16 new_val;
|
|
|
|
return R_SUCCEEDED(this->Read(addr, &new_val)) && new_val == val;
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
bool BatteryDriver::IsPowerOnReset() {
|
|
|
|
/* N doesn't check result... */
|
|
|
|
u16 val = 0;
|
|
|
|
this->Read(Max17050Status, &val);
|
|
|
|
return (val & 0x0002) == 0x0002;
|
2019-05-06 16:00:22 +01:00
|
|
|
}
|
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::LockVfSoc() {
|
|
|
|
return this->Write(Max17050SocVfAccess, 0x0000);
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::UnlockVfSoc() {
|
|
|
|
return this->Write(Max17050SocVfAccess, 0x0080);
|
2019-05-06 16:00:22 +01:00
|
|
|
}
|
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::LockModelTable() {
|
|
|
|
R_TRY(this->Write(Max17050ModelAccess0, 0x0000));
|
|
|
|
R_TRY(this->Write(Max17050ModelAccess1, 0x0000));
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::UnlockModelTable() {
|
|
|
|
R_TRY(this->Write(Max17050ModelAccess0, 0x0059));
|
|
|
|
R_TRY(this->Write(Max17050ModelAccess1, 0x00C4));
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::SetModelTable(const u16 *model_table) {
|
|
|
|
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
|
|
|
R_TRY(this->Write(Max17050ModelChrTblStart + i, model_table[i]));
|
|
|
|
}
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-05-06 16:00:22 +01:00
|
|
|
}
|
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
bool BatteryDriver::IsModelTableLocked() {
|
|
|
|
bool locked = true;
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
u16 cur_val = 0;
|
|
|
|
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
|
|
|
this->Read(Max17050ModelChrTblStart + i, &cur_val);
|
|
|
|
locked &= (cur_val == 0);
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
return locked;
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
bool BatteryDriver::IsModelTableSet(const u16 *model_table) {
|
|
|
|
bool set = true;
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
u16 cur_val = 0;
|
|
|
|
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
|
|
|
this->Read(Max17050ModelChrTblStart + i, &cur_val);
|
|
|
|
set &= (cur_val == model_table[i]);
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
return set;
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::InitializeBatteryParameters() {
|
|
|
|
const Max17050Parameters *params = GetBatteryParameters();
|
|
|
|
|
|
|
|
if (IsPowerOnReset()) {
|
|
|
|
/* Do initial config. */
|
|
|
|
R_TRY(this->ReadWrite(Max17050MiscCfg, 0x8000, 0x8000));
|
|
|
|
|
|
|
|
svcSleepThread(500'000'000ul);
|
|
|
|
|
|
|
|
R_TRY(this->Write(Max17050Config, 0x7210));
|
|
|
|
R_TRY(this->Write(Max17050FilterCfg, 0x8784));
|
|
|
|
R_TRY(this->Write(Max17050RelaxCfg, params->relaxcfg));
|
|
|
|
R_TRY(this->Write(Max17050LearnCfg, 0x2603));
|
|
|
|
R_TRY(this->Write(Max17050FullSocThr, params->fullsocthr));
|
|
|
|
R_TRY(this->Write(Max17050IAvgEmpty, 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 model table. */
|
|
|
|
size_t lock_i = 0;
|
|
|
|
while (true) {
|
|
|
|
lock_i++;
|
|
|
|
R_TRY(this->LockModelTable());
|
|
|
|
|
|
|
|
if (this->IsModelTableLocked()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lock_i >= 8) {
|
|
|
|
/* This is regarded as guaranteed success. */
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
/* Write custom parameters. */
|
|
|
|
while (!this->WriteValidate(Max17050RComp0, params->rcomp0)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050TempCo, params->tempco)) { /* ... */ }
|
|
|
|
|
|
|
|
R_TRY(this->Write(Max17050IChgTerm, params->ichgterm));
|
|
|
|
R_TRY(this->Write(Max17050TGain, params->tgain));
|
|
|
|
R_TRY(this->Write(Max17050TOff, params->toff));
|
|
|
|
|
|
|
|
while (!this->WriteValidate(Max17050VEmpty, params->vempty)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050QResidual00, params->qresidual00)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050QResidual10, params->qresidual10)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050QResidual20, params->qresidual20)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050QResidual30, params->qresidual30)) { /* ... */ }
|
|
|
|
|
|
|
|
|
|
|
|
/* Write full capacity parameters. */
|
|
|
|
while (!this->WriteValidate(Max17050FullCap, params->fullcap)) { /* ... */ }
|
|
|
|
R_TRY(this->Write(Max17050DesignCap, params->vffullcap));
|
|
|
|
while (!this->WriteValidate(Max17050FullCapNom, params->vffullcap)) { /* ... */ }
|
|
|
|
|
|
|
|
svcSleepThread(350'000'000ul);
|
|
|
|
|
|
|
|
/* Write VFSOC to VFSOC 0. */
|
|
|
|
u16 vfsoc, qh;
|
|
|
|
{
|
|
|
|
R_TRY(this->Read(Max17050SocVf, &vfsoc));
|
|
|
|
R_TRY(this->UnlockVfSoc());
|
|
|
|
R_TRY(this->Write(Max17050SocVf0, vfsoc));
|
|
|
|
R_TRY(this->Read(Max17050Qh, &qh));
|
|
|
|
R_TRY(this->Write(Max17050Qh0, qh));
|
|
|
|
R_TRY(this->LockVfSoc());
|
2019-05-06 16:00:22 +01:00
|
|
|
}
|
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
/* Write cycles. */
|
|
|
|
while (!this->WriteValidate(Max17050Cycles, 0x0060)) { /* ... */ }
|
|
|
|
|
|
|
|
/* Load new capacity parameters. */
|
|
|
|
const u16 remcap = static_cast<u16>((vfsoc * params->vffullcap) / 0x6400);
|
|
|
|
const u16 repcap = static_cast<u16>(remcap * (params->fullcap / params->vffullcap));
|
|
|
|
const u16 dqacc = params->vffullcap / 0x10;
|
|
|
|
while (!this->WriteValidate(Max17050RemCapMix, remcap)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050RemCapRep, repcap)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050DPAcc, 0x0C80)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050DQAcc, dqacc)) { /* ... */ }
|
|
|
|
while (!this->WriteValidate(Max17050FullCap, params->fullcap)) { /* ... */ }
|
|
|
|
R_TRY(this->Write(Max17050DesignCap, params->vffullcap));
|
|
|
|
while (!this->WriteValidate(Max17050FullCapNom, params->vffullcap)) { /* ... */ }
|
|
|
|
R_TRY(this->Write(Max17050SocRep, vfsoc));
|
|
|
|
|
|
|
|
/* Finish initialization. */
|
|
|
|
{
|
|
|
|
u16 status;
|
|
|
|
R_TRY(this->Read(Max17050Status, &status));
|
|
|
|
while (!this->WriteValidate(Max17050Status, status & 0xFFFD)) { /* ... */ }
|
2019-05-06 16:00:22 +01:00
|
|
|
}
|
2019-06-22 08:10:21 +01:00
|
|
|
R_TRY(this->Write(Max17050CGain, 0x7FFF));
|
2019-05-06 16:00:22 +01:00
|
|
|
}
|
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-06 16:00:22 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::IsBatteryRemoved(bool *out) {
|
|
|
|
/* N doesn't check result, but we will. */
|
|
|
|
u16 val = 0;
|
|
|
|
R_TRY(this->Read(Max17050Status, &val));
|
|
|
|
*out = (val & 0x0008) == 0x0008;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-05-06 16:00:22 +01:00
|
|
|
}
|
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::GetTemperature(double *out) {
|
|
|
|
u16 val = 0;
|
|
|
|
R_TRY(this->Read(Max17050Temperature, &val));
|
|
|
|
*out = static_cast<double>(val) * double(0.00390625);
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-06 23:53:29 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::GetAverageVCell(u32 *out) {
|
|
|
|
u16 val = 0;
|
|
|
|
R_TRY(this->Read(Max17050AverageVCell, &val));
|
|
|
|
*out = (625 * u32(val >> 3)) / 1000;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-07 07:08:28 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::GetSocRep(double *out) {
|
|
|
|
u16 val = 0;
|
|
|
|
R_TRY(this->Read(Max17050SocRep, &val));
|
|
|
|
*out = static_cast<double>(val) * double(0.00390625);
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-07 07:08:28 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::GetBatteryPercentage(size_t *out) {
|
|
|
|
double raw_charge;
|
|
|
|
R_TRY(this->GetSocRep(&raw_charge));
|
|
|
|
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<size_t>(converted_percentage);
|
|
|
|
}
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-22 08:10:21 +01:00
|
|
|
}
|
2019-05-07 07:08:28 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::SetShutdownTimer() {
|
|
|
|
return this->Write(Max17050ShdnTimer, 0xE000);
|
|
|
|
}
|
2019-05-07 07:08:28 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::GetShutdownEnabled(bool *out) {
|
|
|
|
u16 val = 0;
|
|
|
|
R_TRY(this->Read(Max17050Config, &val));
|
|
|
|
*out = (val & 0x0040) != 0;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-05-07 07:08:28 +01:00
|
|
|
}
|
2019-05-09 10:45:31 +01:00
|
|
|
|
2019-06-22 08:10:21 +01:00
|
|
|
Result BatteryDriver::SetShutdownEnabled(bool enabled) {
|
|
|
|
return this->ReadWrite(Max17050Config, 0x0040, enabled ? 0x0040 : 0x0000);
|
|
|
|
}
|
2019-05-09 10:45:31 +01:00
|
|
|
|
|
|
|
}
|