mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-15 11:33:32 +01:00
Merge pull request #592 from Atmosphere-NX/boot_refactor
Refactor boot sysmodule to use sts::boot namespace.
This commit is contained in:
commit
6bbece39bc
@ -33,7 +33,7 @@ endef
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source source/i2c_driver source/updater
|
||||
SOURCES := source source/i2c source/i2c/driver source/i2c/driver/impl source/gpio source/pinmux
|
||||
DATA := data
|
||||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
@ -14,276 +14,286 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_battery_driver.hpp"
|
||||
#include "boot_calibration.hpp"
|
||||
#include "boot_i2c_utils.hpp"
|
||||
|
||||
const Max17050Parameters *BatteryDriver::GetBatteryParameters() {
|
||||
const u32 battery_version = Boot::GetBatteryVersion();
|
||||
const u32 battery_vendor = Boot::GetBatteryVendor();
|
||||
namespace sts::boot {
|
||||
|
||||
if (battery_version == 2) {
|
||||
if (battery_vendor == 'M') {
|
||||
return &Max17050Params2M;
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result BatteryDriver::Read(u8 addr, u16 *out) {
|
||||
return ReadI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(out), sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result BatteryDriver::Write(u8 addr, u16 val) {
|
||||
return WriteI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(&val), sizeof(val), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result BatteryDriver::ReadWrite(u8 addr, u16 mask, u16 val) {
|
||||
u16 cur_val;
|
||||
R_TRY(this->Read(addr, &cur_val));
|
||||
|
||||
const u16 new_val = (cur_val & ~mask) | val;
|
||||
R_TRY(this->Write(addr, new_val));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
u16 new_val;
|
||||
return R_SUCCEEDED(this->Read(addr, &new_val)) && new_val == val;
|
||||
}
|
||||
|
||||
bool BatteryDriver::IsPowerOnReset() {
|
||||
/* N doesn't check result... */
|
||||
u16 val = 0;
|
||||
this->Read(Max17050Status, &val);
|
||||
return (val & 0x0002) == 0x0002;
|
||||
}
|
||||
|
||||
Result BatteryDriver::LockVfSoc() {
|
||||
return this->Write(Max17050SocVfAccess, 0x0000);
|
||||
}
|
||||
|
||||
Result BatteryDriver::UnlockVfSoc() {
|
||||
return this->Write(Max17050SocVfAccess, 0x0080);
|
||||
}
|
||||
|
||||
Result BatteryDriver::LockModelTable() {
|
||||
R_TRY(this->Write(Max17050ModelAccess0, 0x0000));
|
||||
R_TRY(this->Write(Max17050ModelAccess1, 0x0000));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::UnlockModelTable() {
|
||||
R_TRY(this->Write(Max17050ModelAccess0, 0x0059));
|
||||
R_TRY(this->Write(Max17050ModelAccess1, 0x00C4));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::SetModelTable(const u16 *model_table) {
|
||||
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
||||
R_TRY(this->Write(Max17050ModelChrTblStart + i, model_table[i]));
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool BatteryDriver::IsModelTableLocked() {
|
||||
bool locked = true;
|
||||
|
||||
u16 cur_val = 0;
|
||||
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
||||
this->Read(Max17050ModelChrTblStart + i, &cur_val);
|
||||
locked &= (cur_val == 0);
|
||||
}
|
||||
|
||||
return locked;
|
||||
}
|
||||
|
||||
bool BatteryDriver::IsModelTableSet(const u16 *model_table) {
|
||||
bool set = true;
|
||||
|
||||
u16 cur_val = 0;
|
||||
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
||||
this->Read(Max17050ModelChrTblStart + i, &cur_val);
|
||||
set &= (cur_val == model_table[i]);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
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. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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());
|
||||
}
|
||||
|
||||
/* 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)) { /* ... */ }
|
||||
}
|
||||
R_TRY(this->Write(Max17050CGain, 0x7FFF));
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
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;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::GetTemperature(double *out) {
|
||||
u16 val = 0;
|
||||
R_TRY(this->Read(Max17050Temperature, &val));
|
||||
*out = static_cast<double>(val) * double(0.00390625);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::GetAverageVCell(u32 *out) {
|
||||
u16 val = 0;
|
||||
R_TRY(this->Read(Max17050AverageVCell, &val));
|
||||
*out = (625 * u32(val >> 3)) / 1000;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::GetSocRep(double *out) {
|
||||
u16 val = 0;
|
||||
R_TRY(this->Read(Max17050SocRep, &val));
|
||||
*out = static_cast<double>(val) * double(0.00390625);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
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 {
|
||||
return &Max17050Params2;
|
||||
*out = static_cast<size_t>(converted_percentage);
|
||||
}
|
||||
} else if (battery_version == 1) {
|
||||
return &Max17050Params1;
|
||||
} else {
|
||||
switch (battery_vendor) {
|
||||
case 'M':
|
||||
return &Max17050ParamsM;
|
||||
case 'R':
|
||||
return &Max17050ParamsR;
|
||||
case 'A':
|
||||
default:
|
||||
return &Max17050ParamsA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result BatteryDriver::Read(u8 addr, u16 *out) {
|
||||
return Boot::ReadI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(out), sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result BatteryDriver::Write(u8 addr, u16 val) {
|
||||
return Boot::WriteI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(&val), sizeof(val), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result BatteryDriver::ReadWrite(u8 addr, u16 mask, u16 val) {
|
||||
u16 cur_val;
|
||||
R_TRY(this->Read(addr, &cur_val));
|
||||
|
||||
const u16 new_val = (cur_val & ~mask) | val;
|
||||
R_TRY(this->Write(addr, new_val));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
u16 new_val;
|
||||
return R_SUCCEEDED(this->Read(addr, &new_val)) && new_val == val;
|
||||
}
|
||||
|
||||
bool BatteryDriver::IsPowerOnReset() {
|
||||
/* N doesn't check result... */
|
||||
u16 val = 0;
|
||||
this->Read(Max17050Status, &val);
|
||||
return (val & 0x0002) == 0x0002;
|
||||
}
|
||||
|
||||
Result BatteryDriver::LockVfSoc() {
|
||||
return this->Write(Max17050SocVfAccess, 0x0000);
|
||||
}
|
||||
|
||||
Result BatteryDriver::UnlockVfSoc() {
|
||||
return this->Write(Max17050SocVfAccess, 0x0080);
|
||||
}
|
||||
|
||||
Result BatteryDriver::LockModelTable() {
|
||||
R_TRY(this->Write(Max17050ModelAccess0, 0x0000));
|
||||
R_TRY(this->Write(Max17050ModelAccess1, 0x0000));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::UnlockModelTable() {
|
||||
R_TRY(this->Write(Max17050ModelAccess0, 0x0059));
|
||||
R_TRY(this->Write(Max17050ModelAccess1, 0x00C4));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::SetModelTable(const u16 *model_table) {
|
||||
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
||||
R_TRY(this->Write(Max17050ModelChrTblStart + i, model_table[i]));
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool BatteryDriver::IsModelTableLocked() {
|
||||
bool locked = true;
|
||||
|
||||
u16 cur_val = 0;
|
||||
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
||||
this->Read(Max17050ModelChrTblStart + i, &cur_val);
|
||||
locked &= (cur_val == 0);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return locked;
|
||||
}
|
||||
|
||||
bool BatteryDriver::IsModelTableSet(const u16 *model_table) {
|
||||
bool set = true;
|
||||
|
||||
u16 cur_val = 0;
|
||||
for (size_t i = 0; i < Max17050ModelChrTblSize; i++) {
|
||||
this->Read(Max17050ModelChrTblStart + i, &cur_val);
|
||||
set &= (cur_val == model_table[i]);
|
||||
Result BatteryDriver::SetShutdownTimer() {
|
||||
return this->Write(Max17050ShdnTimer, 0xE000);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
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. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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());
|
||||
}
|
||||
|
||||
/* 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)) { /* ... */ }
|
||||
}
|
||||
R_TRY(this->Write(Max17050CGain, 0x7FFF));
|
||||
Result BatteryDriver::GetShutdownEnabled(bool *out) {
|
||||
u16 val = 0;
|
||||
R_TRY(this->Read(Max17050Config, &val));
|
||||
*out = (val & 0x0040) != 0;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
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;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::GetTemperature(double *out) {
|
||||
u16 val = 0;
|
||||
R_TRY(this->Read(Max17050Temperature, &val));
|
||||
*out = static_cast<double>(val) * double(0.00390625);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::GetAverageVCell(u32 *out) {
|
||||
u16 val = 0;
|
||||
R_TRY(this->Read(Max17050AverageVCell, &val));
|
||||
*out = (625 * u32(val >> 3)) / 1000;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::GetSocRep(double *out) {
|
||||
u16 val = 0;
|
||||
R_TRY(this->Read(Max17050SocRep, &val));
|
||||
*out = static_cast<double>(val) * double(0.00390625);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
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);
|
||||
Result BatteryDriver::SetShutdownEnabled(bool enabled) {
|
||||
return this->ReadWrite(Max17050Config, 0x0040, enabled ? 0x0040 : 0x0000);
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::SetShutdownTimer() {
|
||||
return this->Write(Max17050ShdnTimer, 0xE000);
|
||||
}
|
||||
|
||||
Result BatteryDriver::GetShutdownEnabled(bool *out) {
|
||||
u16 val = 0;
|
||||
R_TRY(this->Read(Max17050Config, &val));
|
||||
*out = (val & 0x0040) != 0;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BatteryDriver::SetShutdownEnabled(bool enabled) {
|
||||
return this->ReadWrite(Max17050Config, 0x0040, enabled ? 0x0040 : 0x0000);
|
||||
}
|
@ -18,47 +18,48 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_driver/i2c_api.hpp"
|
||||
#include "boot_battery_parameters.hpp"
|
||||
#include "i2c/driver/i2c_api.hpp"
|
||||
|
||||
class BatteryDriver {
|
||||
private:
|
||||
I2cSessionImpl i2c_session;
|
||||
public:
|
||||
BatteryDriver() {
|
||||
I2cDriver::Initialize();
|
||||
I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Max17050);
|
||||
}
|
||||
namespace sts::boot {
|
||||
|
||||
~BatteryDriver() {
|
||||
I2cDriver::CloseSession(this->i2c_session);
|
||||
I2cDriver::Finalize();
|
||||
}
|
||||
private:
|
||||
static const Max17050Parameters *GetBatteryParameters();
|
||||
class BatteryDriver {
|
||||
private:
|
||||
i2c::driver::Session i2c_session;
|
||||
public:
|
||||
BatteryDriver() {
|
||||
i2c::driver::Initialize();
|
||||
i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Max17050);
|
||||
}
|
||||
|
||||
Result Read(u8 addr, u16 *out_data);
|
||||
Result Write(u8 addr, u16 val);
|
||||
Result ReadWrite(u8 addr, u16 mask, u16 val);
|
||||
bool WriteValidate(u8 addr, u16 val);
|
||||
~BatteryDriver() {
|
||||
i2c::driver::CloseSession(this->i2c_session);
|
||||
i2c::driver::Finalize();
|
||||
}
|
||||
private:
|
||||
Result Read(u8 addr, u16 *out_data);
|
||||
Result Write(u8 addr, u16 val);
|
||||
Result ReadWrite(u8 addr, u16 mask, u16 val);
|
||||
bool WriteValidate(u8 addr, u16 val);
|
||||
|
||||
bool IsPowerOnReset();
|
||||
Result LockVfSoc();
|
||||
Result UnlockVfSoc();
|
||||
Result LockModelTable();
|
||||
Result UnlockModelTable();
|
||||
bool IsModelTableLocked();
|
||||
Result SetModelTable(const u16 *model_table);
|
||||
bool IsModelTableSet(const u16 *model_table);
|
||||
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:
|
||||
Result InitializeBatteryParameters();
|
||||
Result IsBatteryRemoved(bool *out);
|
||||
Result GetTemperature(double *out);
|
||||
Result GetAverageVCell(u32 *out);
|
||||
Result GetSocRep(double *out);
|
||||
Result GetBatteryPercentage(size_t *out);
|
||||
Result SetShutdownTimer();
|
||||
Result GetShutdownEnabled(bool *out);
|
||||
Result SetShutdownEnabled(bool enabled);
|
||||
};
|
||||
public:
|
||||
Result InitializeBatteryParameters();
|
||||
Result IsBatteryRemoved(bool *out);
|
||||
Result GetTemperature(double *out);
|
||||
Result GetAverageVCell(u32 *out);
|
||||
Result GetSocRep(double *out);
|
||||
Result GetBatteryPercentage(size_t *out);
|
||||
Result SetShutdownTimer();
|
||||
Result GetShutdownEnabled(bool *out);
|
||||
Result SetShutdownEnabled(bool enabled);
|
||||
};
|
||||
|
||||
}
|
||||
|
29
stratosphere/boot/source/boot_battery_icon_charging.inc
Normal file
29
stratosphere/boot/source/boot_battery_icon_charging.inc
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
23
stratosphere/boot/source/boot_battery_icon_low.inc
Normal file
23
stratosphere/boot/source/boot_battery_icon_low.inc
Normal file
File diff suppressed because one or more lines are too long
@ -14,70 +14,82 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_battery_icon_low.hpp"
|
||||
#include "boot_battery_icon_charging.hpp"
|
||||
#include "boot_battery_icon_charging_red.hpp"
|
||||
#include "boot_battery_icons.hpp"
|
||||
#include "boot_display.hpp"
|
||||
|
||||
void Boot::ShowLowBatteryIcon() {
|
||||
Boot::InitializeDisplay();
|
||||
{
|
||||
/* Low battery icon is shown for 5 seconds. */
|
||||
Boot::ShowDisplay(LowBatteryX, LowBatteryY, LowBatteryW, LowBatteryH, LowBattery);
|
||||
svcSleepThread(5'000'000'000ul);
|
||||
}
|
||||
Boot::FinalizeDisplay();
|
||||
}
|
||||
namespace sts::boot {
|
||||
|
||||
static void FillBatteryMeter(u32 *icon, const size_t icon_w, const size_t icon_h, const size_t meter_x, const size_t meter_y, const size_t meter_w, const size_t meter_h, const size_t fill_w) {
|
||||
const size_t fill_x = meter_x + meter_w - fill_w;
|
||||
namespace {
|
||||
|
||||
if (fill_x + fill_w > icon_w || meter_y + meter_h > icon_h || fill_x == 0) {
|
||||
return;
|
||||
}
|
||||
/* Pull in icon definitions. */
|
||||
#include "boot_battery_icon_low.inc"
|
||||
#include "boot_battery_icon_charging.inc"
|
||||
#include "boot_battery_icon_charging_red.inc"
|
||||
|
||||
u32 *cur_row = icon + meter_y * icon_w + fill_x;
|
||||
for (size_t y = 0; y < meter_h; y++) {
|
||||
/* Make last column of meter identical to first column of meter. */
|
||||
cur_row[-1] = icon[(meter_y + y) * icon_w + meter_x];
|
||||
/* Helpers. */
|
||||
void FillBatteryMeter(u32 *icon, const size_t icon_w, const size_t icon_h, const size_t meter_x, const size_t meter_y, const size_t meter_w, const size_t meter_h, const size_t fill_w) {
|
||||
const size_t fill_x = meter_x + meter_w - fill_w;
|
||||
|
||||
/* Black out further pixels. */
|
||||
for (size_t x = 0; x < fill_w; x++) {
|
||||
cur_row[x] = 0xFF000000;
|
||||
if (fill_x + fill_w > icon_w || meter_y + meter_h > icon_h || fill_x == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
u32 *cur_row = icon + meter_y * icon_w + fill_x;
|
||||
for (size_t y = 0; y < meter_h; y++) {
|
||||
/* Make last column of meter identical to first column of meter. */
|
||||
cur_row[-1] = icon[(meter_y + y) * icon_w + meter_x];
|
||||
|
||||
/* Black out further pixels. */
|
||||
for (size_t x = 0; x < fill_w; x++) {
|
||||
cur_row[x] = 0xFF000000;
|
||||
}
|
||||
cur_row += icon_w;
|
||||
}
|
||||
}
|
||||
cur_row += icon_w;
|
||||
}
|
||||
}
|
||||
|
||||
void Boot::StartShowChargingIcon(size_t battery_percentage, bool wait) {
|
||||
const bool is_red = battery_percentage <= 15;
|
||||
|
||||
const size_t IconX = is_red ? ChargingRedBatteryX : ChargingBatteryX;
|
||||
const size_t IconY = is_red ? ChargingRedBatteryY : ChargingBatteryY;
|
||||
const size_t IconW = is_red ? ChargingRedBatteryW : ChargingBatteryW;
|
||||
const size_t IconH = is_red ? ChargingRedBatteryH : ChargingBatteryH;
|
||||
const size_t IconMeterX = is_red ? ChargingRedBatteryMeterX : ChargingBatteryMeterX;
|
||||
const size_t IconMeterY = is_red ? ChargingRedBatteryMeterY : ChargingBatteryMeterY;
|
||||
const size_t IconMeterW = is_red ? ChargingRedBatteryMeterW : ChargingBatteryMeterW;
|
||||
const size_t IconMeterH = is_red ? ChargingRedBatteryMeterH : ChargingBatteryMeterH;
|
||||
const size_t MeterFillW = static_cast<size_t>(IconMeterW * (1.0 - (0.0404 + 0.0096 * battery_percentage)) + 0.5);
|
||||
|
||||
/* Create stack buffer, copy icon into it, draw fill meter, draw. */
|
||||
{
|
||||
u32 Icon[IconW * IconH];
|
||||
std::memcpy(Icon, is_red ? ChargingRedBattery : ChargingBattery, sizeof(Icon));
|
||||
FillBatteryMeter(Icon, IconW, IconH, IconMeterX, IconMeterY, IconMeterW, IconMeterH, MeterFillW);
|
||||
|
||||
Boot::InitializeDisplay();
|
||||
Boot::ShowDisplay(IconX, IconY, IconW, IconH, Icon);
|
||||
}
|
||||
|
||||
/* Wait for 2 seconds if we're supposed to. */
|
||||
if (wait) {
|
||||
svcSleepThread(2'000'000'000ul);
|
||||
void ShowLowBatteryIcon() {
|
||||
InitializeDisplay();
|
||||
{
|
||||
/* Low battery icon is shown for 5 seconds. */
|
||||
ShowDisplay(LowBatteryX, LowBatteryY, LowBatteryW, LowBatteryH, LowBattery);
|
||||
svcSleepThread(5'000'000'000ul);
|
||||
}
|
||||
FinalizeDisplay();
|
||||
}
|
||||
|
||||
void StartShowChargingIcon(size_t battery_percentage, bool wait) {
|
||||
const bool is_red = battery_percentage <= 15;
|
||||
|
||||
const size_t IconX = is_red ? ChargingRedBatteryX : ChargingBatteryX;
|
||||
const size_t IconY = is_red ? ChargingRedBatteryY : ChargingBatteryY;
|
||||
const size_t IconW = is_red ? ChargingRedBatteryW : ChargingBatteryW;
|
||||
const size_t IconH = is_red ? ChargingRedBatteryH : ChargingBatteryH;
|
||||
const size_t IconMeterX = is_red ? ChargingRedBatteryMeterX : ChargingBatteryMeterX;
|
||||
const size_t IconMeterY = is_red ? ChargingRedBatteryMeterY : ChargingBatteryMeterY;
|
||||
const size_t IconMeterW = is_red ? ChargingRedBatteryMeterW : ChargingBatteryMeterW;
|
||||
const size_t IconMeterH = is_red ? ChargingRedBatteryMeterH : ChargingBatteryMeterH;
|
||||
const size_t MeterFillW = static_cast<size_t>(IconMeterW * (1.0 - (0.0404 + 0.0096 * battery_percentage)) + 0.5);
|
||||
|
||||
/* Create stack buffer, copy icon into it, draw fill meter, draw. */
|
||||
{
|
||||
u32 Icon[IconW * IconH];
|
||||
std::memcpy(Icon, is_red ? ChargingRedBattery : ChargingBattery, sizeof(Icon));
|
||||
FillBatteryMeter(Icon, IconW, IconH, IconMeterX, IconMeterY, IconMeterW, IconMeterH, MeterFillW);
|
||||
|
||||
InitializeDisplay();
|
||||
ShowDisplay(IconX, IconY, IconW, IconH, Icon);
|
||||
}
|
||||
|
||||
/* Wait for 2 seconds if we're supposed to. */
|
||||
if (wait) {
|
||||
svcSleepThread(2'000'000'000ul);
|
||||
}
|
||||
}
|
||||
|
||||
void EndShowChargingIcon() {
|
||||
FinalizeDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
void Boot::EndShowChargingIcon() {
|
||||
Boot::FinalizeDisplay();
|
||||
}
|
||||
|
@ -18,13 +18,11 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#define IRAM_BASE 0x40000000ull
|
||||
#define IRAM_SIZE 0x40000
|
||||
#define IRAM_PAYLOAD_MAX_SIZE 0x2E000
|
||||
#define IRAM_PAYLOAD_BASE 0x40010000ull
|
||||
namespace sts::boot {
|
||||
|
||||
class BootRebootManager {
|
||||
public:
|
||||
static Result PerformReboot();
|
||||
static void RebootForFatalError(AtmosphereFatalErrorContext *ctx);
|
||||
};
|
||||
/* Battery Display utilities. */
|
||||
void ShowLowBatteryIcon();
|
||||
void StartShowChargingIcon(size_t battery_percentage, bool wait);
|
||||
void EndShowChargingIcon();
|
||||
|
||||
}
|
@ -14,99 +14,96 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
constexpr u8 Max17050Status = 0x00;
|
||||
constexpr u8 Max17050VAlrtThreshold = 0x01;
|
||||
constexpr u8 Max17050TAlrtThreshold = 0x02;
|
||||
constexpr u8 Max17050SocAlrtThreshold = 0x03;
|
||||
constexpr u8 Max17050AtRate = 0x04;
|
||||
constexpr u8 Max17050RemCapRep = 0x05;
|
||||
constexpr u8 Max17050SocRep = 0x06;
|
||||
constexpr u8 Max17050Age = 0x07;
|
||||
constexpr u8 Max17050Temperature = 0x08;
|
||||
constexpr u8 Max17050VCell = 0x09;
|
||||
constexpr u8 Max17050Current = 0x0A;
|
||||
constexpr u8 Max17050AverageCurrent = 0x0B;
|
||||
|
||||
static constexpr u8 Max17050Status = 0x00;
|
||||
static constexpr u8 Max17050VAlrtThreshold = 0x01;
|
||||
static constexpr u8 Max17050TAlrtThreshold = 0x02;
|
||||
static constexpr u8 Max17050SocAlrtThreshold = 0x03;
|
||||
static constexpr u8 Max17050AtRate = 0x04;
|
||||
static constexpr u8 Max17050RemCapRep = 0x05;
|
||||
static constexpr u8 Max17050SocRep = 0x06;
|
||||
static constexpr u8 Max17050Age = 0x07;
|
||||
static constexpr u8 Max17050Temperature = 0x08;
|
||||
static constexpr u8 Max17050VCell = 0x09;
|
||||
static constexpr u8 Max17050Current = 0x0A;
|
||||
static constexpr u8 Max17050AverageCurrent = 0x0B;
|
||||
|
||||
static constexpr u8 Max17050SocMix = 0x0D;
|
||||
static constexpr u8 Max17050SocAv = 0x0E;
|
||||
static constexpr u8 Max17050RemCapMix = 0x0F;
|
||||
static constexpr u8 Max17050FullCap = 0x10;
|
||||
static constexpr u8 Max17050Tte = 0x11;
|
||||
static constexpr u8 Max17050QResidual00 = 0x12;
|
||||
static constexpr u8 Max17050FullSocThr = 0x13;
|
||||
constexpr u8 Max17050SocMix = 0x0D;
|
||||
constexpr u8 Max17050SocAv = 0x0E;
|
||||
constexpr u8 Max17050RemCapMix = 0x0F;
|
||||
constexpr u8 Max17050FullCap = 0x10;
|
||||
constexpr u8 Max17050Tte = 0x11;
|
||||
constexpr u8 Max17050QResidual00 = 0x12;
|
||||
constexpr u8 Max17050FullSocThr = 0x13;
|
||||
|
||||
|
||||
static constexpr u8 Max17050AverageTemp = 0x16;
|
||||
static constexpr u8 Max17050Cycles = 0x17;
|
||||
static constexpr u8 Max17050DesignCap = 0x18;
|
||||
static constexpr u8 Max17050AverageVCell = 0x19;
|
||||
static constexpr u8 Max17050MaxMinTemp = 0x1A;
|
||||
static constexpr u8 Max17050MaxMinVoltage = 0x1B;
|
||||
static constexpr u8 Max17050MaxMinCurrent = 0x1C;
|
||||
static constexpr u8 Max17050Config = 0x1D;
|
||||
static constexpr u8 Max17050IChgTerm = 0x1E;
|
||||
static constexpr u8 Max17050RemCapAv = 0x1F;
|
||||
constexpr u8 Max17050AverageTemp = 0x16;
|
||||
constexpr u8 Max17050Cycles = 0x17;
|
||||
constexpr u8 Max17050DesignCap = 0x18;
|
||||
constexpr u8 Max17050AverageVCell = 0x19;
|
||||
constexpr u8 Max17050MaxMinTemp = 0x1A;
|
||||
constexpr u8 Max17050MaxMinVoltage = 0x1B;
|
||||
constexpr u8 Max17050MaxMinCurrent = 0x1C;
|
||||
constexpr u8 Max17050Config = 0x1D;
|
||||
constexpr u8 Max17050IChgTerm = 0x1E;
|
||||
constexpr u8 Max17050RemCapAv = 0x1F;
|
||||
|
||||
static constexpr u8 Max17050Version = 0x21;
|
||||
static constexpr u8 Max17050QResidual10 = 0x22;
|
||||
static constexpr u8 Max17050FullCapNom = 0x23;
|
||||
static constexpr u8 Max17050TempNom = 0x24;
|
||||
static constexpr u8 Max17050TempLim = 0x25;
|
||||
constexpr u8 Max17050Version = 0x21;
|
||||
constexpr u8 Max17050QResidual10 = 0x22;
|
||||
constexpr u8 Max17050FullCapNom = 0x23;
|
||||
constexpr u8 Max17050TempNom = 0x24;
|
||||
constexpr u8 Max17050TempLim = 0x25;
|
||||
|
||||
static constexpr u8 Max17050Ain = 0x27;
|
||||
static constexpr u8 Max17050LearnCfg = 0x28;
|
||||
static constexpr u8 Max17050FilterCfg = 0x29;
|
||||
static constexpr u8 Max17050RelaxCfg = 0x2A;
|
||||
static constexpr u8 Max17050MiscCfg = 0x2B;
|
||||
static constexpr u8 Max17050TGain = 0x2C;
|
||||
static constexpr u8 Max17050TOff = 0x2D;
|
||||
static constexpr u8 Max17050CGain = 0x2E;
|
||||
static constexpr u8 Max17050COff = 0x2F;
|
||||
constexpr u8 Max17050Ain = 0x27;
|
||||
constexpr u8 Max17050LearnCfg = 0x28;
|
||||
constexpr u8 Max17050FilterCfg = 0x29;
|
||||
constexpr u8 Max17050RelaxCfg = 0x2A;
|
||||
constexpr u8 Max17050MiscCfg = 0x2B;
|
||||
constexpr u8 Max17050TGain = 0x2C;
|
||||
constexpr u8 Max17050TOff = 0x2D;
|
||||
constexpr u8 Max17050CGain = 0x2E;
|
||||
constexpr u8 Max17050COff = 0x2F;
|
||||
|
||||
|
||||
static constexpr u8 Max17050QResidual20 = 0x32;
|
||||
constexpr u8 Max17050QResidual20 = 0x32;
|
||||
|
||||
|
||||
|
||||
static constexpr u8 Max17050IAvgEmpty = 0x36;
|
||||
static constexpr u8 Max17050FCtc = 0x37;
|
||||
static constexpr u8 Max17050RComp0 = 0x38;
|
||||
static constexpr u8 Max17050TempCo = 0x39;
|
||||
static constexpr u8 Max17050VEmpty = 0x3A;
|
||||
constexpr u8 Max17050IAvgEmpty = 0x36;
|
||||
constexpr u8 Max17050FCtc = 0x37;
|
||||
constexpr u8 Max17050RComp0 = 0x38;
|
||||
constexpr u8 Max17050TempCo = 0x39;
|
||||
constexpr u8 Max17050VEmpty = 0x3A;
|
||||
|
||||
|
||||
static constexpr u8 Max17050FStat = 0x3D;
|
||||
static constexpr u8 Max17050Timer = 0x3E;
|
||||
static constexpr u8 Max17050ShdnTimer = 0x3F;
|
||||
constexpr u8 Max17050FStat = 0x3D;
|
||||
constexpr u8 Max17050Timer = 0x3E;
|
||||
constexpr u8 Max17050ShdnTimer = 0x3F;
|
||||
|
||||
|
||||
static constexpr u8 Max17050QResidual30 = 0x42;
|
||||
constexpr u8 Max17050QResidual30 = 0x42;
|
||||
|
||||
|
||||
static constexpr u8 Max17050DQAcc = 0x45;
|
||||
static constexpr u8 Max17050DPAcc = 0x46;
|
||||
constexpr u8 Max17050DQAcc = 0x45;
|
||||
constexpr u8 Max17050DPAcc = 0x46;
|
||||
|
||||
static constexpr u8 Max17050SocVf0 = 0x48;
|
||||
constexpr u8 Max17050SocVf0 = 0x48;
|
||||
|
||||
static constexpr u8 Max17050Qh0 = 0x4C;
|
||||
static constexpr u8 Max17050Qh = 0x4D;
|
||||
constexpr u8 Max17050Qh0 = 0x4C;
|
||||
constexpr u8 Max17050Qh = 0x4D;
|
||||
|
||||
static constexpr u8 Max17050SocVfAccess = 0x60;
|
||||
constexpr u8 Max17050SocVfAccess = 0x60;
|
||||
|
||||
static constexpr u8 Max17050ModelAccess0 = 0x62;
|
||||
static constexpr u8 Max17050ModelAccess1 = 0x63;
|
||||
constexpr u8 Max17050ModelAccess0 = 0x62;
|
||||
constexpr u8 Max17050ModelAccess1 = 0x63;
|
||||
|
||||
static constexpr u8 Max17050ModelChrTblStart = 0x80;
|
||||
static constexpr u8 Max17050ModelChrTblEnd = 0xB0;
|
||||
constexpr u8 Max17050ModelChrTblStart = 0x80;
|
||||
constexpr u8 Max17050ModelChrTblEnd = 0xB0;
|
||||
|
||||
|
||||
static constexpr u8 Max17050VFocV = 0xFB;
|
||||
static constexpr u8 Max17050SocVf = 0xFF;
|
||||
constexpr u8 Max17050VFocV = 0xFB;
|
||||
constexpr u8 Max17050SocVf = 0xFF;
|
||||
|
||||
static constexpr size_t Max17050ModelChrTblSize = Max17050ModelChrTblEnd - Max17050ModelChrTblStart;
|
||||
constexpr size_t Max17050ModelChrTblSize = Max17050ModelChrTblEnd - Max17050ModelChrTblStart;
|
||||
|
||||
struct Max17050Parameters {
|
||||
u16 relaxcfg;
|
||||
@ -129,7 +126,7 @@ struct Max17050Parameters {
|
||||
|
||||
static_assert(sizeof(Max17050Parameters) == 0x7E, "Max17050Parameters definition!");
|
||||
|
||||
static constexpr Max17050Parameters Max17050ParamsA = {
|
||||
constexpr Max17050Parameters Max17050ParamsA = {
|
||||
0x203B, /* relaxcfg */
|
||||
0x0053, /* rcomp0 */
|
||||
0x1C22, /* tempco */
|
||||
@ -155,7 +152,7 @@ static constexpr Max17050Parameters Max17050ParamsA = {
|
||||
0x1D2A /* iavgempty */
|
||||
};
|
||||
|
||||
static constexpr Max17050Parameters Max17050ParamsM = {
|
||||
constexpr Max17050Parameters Max17050ParamsM = {
|
||||
0x203B, /* relaxcfg */
|
||||
0x0085, /* rcomp0 */
|
||||
0x1625, /* tempco */
|
||||
@ -181,7 +178,7 @@ static constexpr Max17050Parameters Max17050ParamsM = {
|
||||
0x1D2A /* iavgempty */
|
||||
};
|
||||
|
||||
static constexpr Max17050Parameters Max17050ParamsR = {
|
||||
constexpr Max17050Parameters Max17050ParamsR = {
|
||||
0x203B, /* relaxcfg */
|
||||
0x0048, /* rcomp0 */
|
||||
0x2034, /* tempco */
|
||||
@ -207,7 +204,7 @@ static constexpr Max17050Parameters Max17050ParamsR = {
|
||||
0x1D2A /* iavgempty */
|
||||
};
|
||||
|
||||
static constexpr Max17050Parameters Max17050Params1 = {
|
||||
constexpr Max17050Parameters Max17050Params1 = {
|
||||
0x203B, /* relaxcfg */
|
||||
0x0040, /* rcomp0 */
|
||||
0x1624, /* tempco */
|
||||
@ -233,7 +230,7 @@ static constexpr Max17050Parameters Max17050Params1 = {
|
||||
0x1584 /* iavgempty */
|
||||
};
|
||||
|
||||
static constexpr Max17050Parameters Max17050Params2 = {
|
||||
constexpr Max17050Parameters Max17050Params2 = {
|
||||
0x203B, /* relaxcfg */
|
||||
0x004A, /* rcomp0 */
|
||||
0x1D23, /* tempco */
|
||||
@ -259,7 +256,7 @@ static constexpr Max17050Parameters Max17050Params2 = {
|
||||
0x1680 /* iavgempty */
|
||||
};
|
||||
|
||||
static constexpr Max17050Parameters Max17050Params2M = {
|
||||
constexpr Max17050Parameters Max17050Params2M = {
|
||||
0x203B, /* relaxcfg */
|
||||
0x0049, /* rcomp0 */
|
||||
0x222A, /* tempco */
|
@ -14,100 +14,113 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include <stratosphere/spl.hpp>
|
||||
|
||||
#include "boot_boot_reason.hpp"
|
||||
#include "boot_pmic_driver.hpp"
|
||||
#include "boot_rtc_driver.hpp"
|
||||
|
||||
static u32 g_boot_reason = 0;
|
||||
static bool g_detected_boot_reason = false;
|
||||
namespace sts::boot {
|
||||
|
||||
struct BootReasonValue {
|
||||
union {
|
||||
struct {
|
||||
u8 power_intr;
|
||||
u8 rtc_intr;
|
||||
u8 nv_erc;
|
||||
u8 boot_reason;
|
||||
namespace {
|
||||
|
||||
/* Types. */
|
||||
struct BootReasonValue {
|
||||
union {
|
||||
struct {
|
||||
u8 power_intr;
|
||||
u8 rtc_intr;
|
||||
u8 nv_erc;
|
||||
u8 boot_reason;
|
||||
};
|
||||
u32 value;
|
||||
};
|
||||
};
|
||||
u32 value;
|
||||
};
|
||||
};
|
||||
|
||||
static u32 MakeBootReason(u32 power_intr, u8 rtc_intr, u8 nv_erc, bool ac_ok) {
|
||||
if (power_intr & 0x08) {
|
||||
return 2;
|
||||
}
|
||||
if (rtc_intr & 0x02) {
|
||||
return 3;
|
||||
}
|
||||
if (power_intr & 0x80) {
|
||||
return 1;
|
||||
}
|
||||
if (rtc_intr & 0x04) {
|
||||
if (nv_erc != 0x80 && !Boot::IsRecoveryBoot()) {
|
||||
return 4;
|
||||
/* Globals. */
|
||||
u32 g_boot_reason = 0;
|
||||
bool g_detected_boot_reason = false;
|
||||
|
||||
/* Helpers. */
|
||||
u32 MakeBootReason(u32 power_intr, u8 rtc_intr, u8 nv_erc, bool ac_ok) {
|
||||
if (power_intr & 0x08) {
|
||||
return 2;
|
||||
}
|
||||
if (rtc_intr & 0x02) {
|
||||
return 3;
|
||||
}
|
||||
if (power_intr & 0x80) {
|
||||
return 1;
|
||||
}
|
||||
if (rtc_intr & 0x04) {
|
||||
if (nv_erc != 0x80 && !spl::IsRecoveryBoot()) {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
if ((nv_erc & 0x40) && ac_ok) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((nv_erc & 0x40) && ac_ok) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Boot::DetectBootReason() {
|
||||
u8 power_intr;
|
||||
u8 rtc_intr;
|
||||
u8 rtc_intr_m;
|
||||
u8 nv_erc;
|
||||
bool ac_ok;
|
||||
}
|
||||
|
||||
/* Get values from PMIC. */
|
||||
{
|
||||
PmicDriver pmic_driver;
|
||||
if (R_FAILED(pmic_driver.GetPowerIntr(&power_intr))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pmic_driver.GetNvErc(&nv_erc))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pmic_driver.GetAcOk(&ac_ok))) {
|
||||
void DetectBootReason() {
|
||||
u8 power_intr;
|
||||
u8 rtc_intr;
|
||||
u8 rtc_intr_m;
|
||||
u8 nv_erc;
|
||||
bool ac_ok;
|
||||
|
||||
/* Get values from PMIC. */
|
||||
{
|
||||
PmicDriver pmic_driver;
|
||||
if (R_FAILED(pmic_driver.GetPowerIntr(&power_intr))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pmic_driver.GetNvErc(&nv_erc))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pmic_driver.GetAcOk(&ac_ok))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
/* Get values from RTC. */
|
||||
{
|
||||
RtcDriver rtc_driver;
|
||||
if (R_FAILED(rtc_driver.GetRtcIntr(&rtc_intr))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(rtc_driver.GetRtcIntrM(&rtc_intr_m))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
/* Set global derived boot reason. */
|
||||
g_boot_reason = MakeBootReason(power_intr, rtc_intr & ~rtc_intr_m, nv_erc, ac_ok);
|
||||
|
||||
/* Set boot reason for SPL. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
|
||||
BootReasonValue boot_reason_value;
|
||||
boot_reason_value.power_intr = power_intr;
|
||||
boot_reason_value.rtc_intr = rtc_intr & ~rtc_intr_m;
|
||||
boot_reason_value.nv_erc = nv_erc;
|
||||
boot_reason_value.boot_reason = g_boot_reason;
|
||||
if (R_FAILED(splSetBootReason(boot_reason_value.value))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
g_detected_boot_reason = true;
|
||||
}
|
||||
|
||||
u32 GetBootReason() {
|
||||
if (!g_detected_boot_reason) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return g_boot_reason;
|
||||
}
|
||||
|
||||
/* Get values from RTC. */
|
||||
{
|
||||
RtcDriver rtc_driver;
|
||||
if (R_FAILED(rtc_driver.GetRtcIntr(&rtc_intr))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(rtc_driver.GetRtcIntrM(&rtc_intr_m))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
/* Set global derived boot reason. */
|
||||
g_boot_reason = MakeBootReason(power_intr, rtc_intr & ~rtc_intr_m, nv_erc, ac_ok);
|
||||
|
||||
/* Set boot reason for SPL. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
|
||||
BootReasonValue boot_reason_value;
|
||||
boot_reason_value.power_intr = power_intr;
|
||||
boot_reason_value.rtc_intr = rtc_intr & ~rtc_intr_m;
|
||||
boot_reason_value.nv_erc = nv_erc;
|
||||
boot_reason_value.boot_reason = g_boot_reason;
|
||||
if (R_FAILED(splSetBootReason(boot_reason_value.value))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
g_detected_boot_reason = true;
|
||||
}
|
||||
|
||||
u32 Boot::GetBootReason() {
|
||||
if (!g_detected_boot_reason) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return g_boot_reason;
|
||||
}
|
||||
}
|
@ -18,16 +18,10 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
/* pcv isn't alive at the time boot runs, but nn::i2c::driver needs nn::pcv. */
|
||||
/* These are the overrides N puts in boot. */
|
||||
namespace sts::boot {
|
||||
|
||||
class Pcv {
|
||||
public:
|
||||
static void Initialize();
|
||||
static void Finalize();
|
||||
static Result SetClockRate(PcvModule module, u32 hz);
|
||||
static Result SetClockEnabled(PcvModule module, bool enabled);
|
||||
static Result SetVoltageEnabled(u32 domain, bool enabled);
|
||||
static Result SetVoltageValue(u32 domain, u32 voltage);
|
||||
static Result SetReset(PcvModule module, bool reset);
|
||||
};
|
||||
/* Boot Reason utilities. */
|
||||
void DetectBootReason();
|
||||
u32 GetBootReason();
|
||||
|
||||
}
|
@ -14,126 +14,127 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
namespace sts::boot::bq24193 {
|
||||
|
||||
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;
|
||||
constexpr u8 InputSourceControl = 0x00;
|
||||
constexpr u8 PowerOnConfiguration = 0x01;
|
||||
constexpr u8 ChargeCurrentControl = 0x02;
|
||||
constexpr u8 PreChargeTerminationCurrentControl = 0x03;
|
||||
constexpr u8 ChargeVoltageControl = 0x04;
|
||||
constexpr u8 ChargeTerminationTimerControl = 0x05;
|
||||
constexpr u8 IrCompensationThermalRegulationControl = 0x06;
|
||||
constexpr u8 MiscOperationControl = 0x07;
|
||||
constexpr u8 SystemStatus = 0x08;
|
||||
constexpr u8 Fault = 0x09;
|
||||
constexpr u8 VendorPartRevisionStatus = 0x0A;
|
||||
|
||||
enum ChargerConfiguration : u8 {
|
||||
ChargerConfiguration_ChargeDisable = (0 << 4),
|
||||
ChargerConfiguration_ChargeBattery = (1 << 4),
|
||||
ChargerConfiguration_Otg = (2 << 4),
|
||||
};
|
||||
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;
|
||||
constexpr u32 ChargeVoltageLimitMin = 3504;
|
||||
constexpr u32 ChargeVoltageLimitMax = 4208;
|
||||
|
||||
static inline u8 EncodeChargeVoltageLimit(u32 voltage) {
|
||||
if (voltage < ChargeVoltageLimitMin || voltage > ChargeVoltageLimitMax) {
|
||||
std::abort();
|
||||
inline u8 EncodeChargeVoltageLimit(u32 voltage) {
|
||||
if (voltage < ChargeVoltageLimitMin || voltage > ChargeVoltageLimitMax) {
|
||||
std::abort();
|
||||
}
|
||||
voltage -= ChargeVoltageLimitMin;
|
||||
voltage >>= 4;
|
||||
return static_cast<u8>(voltage << 2);
|
||||
}
|
||||
voltage -= ChargeVoltageLimitMin;
|
||||
voltage >>= 4;
|
||||
return static_cast<u8>(voltage << 2);
|
||||
}
|
||||
|
||||
static inline u32 DecodeChargeVoltageLimit(u8 reg) {
|
||||
return ChargeVoltageLimitMin + (static_cast<u32>(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();
|
||||
inline u32 DecodeChargeVoltageLimit(u8 reg) {
|
||||
return ChargeVoltageLimitMin + (static_cast<u32>(reg & 0xFC) << 2);
|
||||
}
|
||||
current -= FastChargeCurrentLimitMin;
|
||||
current >>= 6;
|
||||
return static_cast<u8>(current << 2);
|
||||
}
|
||||
|
||||
static inline u32 DecodeFastChargeCurrentLimit(u8 reg) {
|
||||
return FastChargeCurrentLimitMin + (static_cast<u32>(reg & 0xFC) << 4);
|
||||
}
|
||||
constexpr u32 FastChargeCurrentLimitMin = 512;
|
||||
constexpr u32 FastChargeCurrentLimitMax = 4544;
|
||||
|
||||
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();
|
||||
inline u8 EncodeFastChargeCurrentLimit(u32 current) {
|
||||
if (current < FastChargeCurrentLimitMin || current > FastChargeCurrentLimitMax) {
|
||||
std::abort();
|
||||
}
|
||||
current -= FastChargeCurrentLimitMin;
|
||||
current >>= 6;
|
||||
return static_cast<u8>(current << 2);
|
||||
}
|
||||
current -= PreChargeCurrentLimitMin;
|
||||
current >>= 7;
|
||||
return static_cast<u8>(current << 4);
|
||||
}
|
||||
|
||||
static inline u32 DecodePreChargeCurrentLimit(u8 reg) {
|
||||
return PreChargeCurrentLimitMin + (static_cast<u32>(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();
|
||||
inline u32 DecodeFastChargeCurrentLimit(u8 reg) {
|
||||
return FastChargeCurrentLimitMin + (static_cast<u32>(reg & 0xFC) << 4);
|
||||
}
|
||||
current -= TerminationCurrentLimitMin;
|
||||
current >>= 7;
|
||||
return static_cast<u8>(current);
|
||||
}
|
||||
|
||||
static inline u32 DecodeTerminationCurrentLimit(u8 reg) {
|
||||
return TerminationCurrentLimitMin + (static_cast<u32>(reg & 0xF) << 7);
|
||||
}
|
||||
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 MinimumSystemVoltageLimitMin = 3000;
|
||||
static constexpr u32 MinimumSystemVoltageLimitMax = 3700;
|
||||
constexpr u32 PreChargeCurrentLimitMin = 128;
|
||||
constexpr u32 PreChargeCurrentLimitMax = 2048;
|
||||
|
||||
static inline u8 EncodeMinimumSystemVoltageLimit(u32 voltage) {
|
||||
if (voltage < MinimumSystemVoltageLimitMin || voltage > MinimumSystemVoltageLimitMax) {
|
||||
std::abort();
|
||||
inline u8 EncodePreChargeCurrentLimit(u32 current) {
|
||||
if (current < PreChargeCurrentLimitMin || current > PreChargeCurrentLimitMax) {
|
||||
std::abort();
|
||||
}
|
||||
current -= PreChargeCurrentLimitMin;
|
||||
current >>= 7;
|
||||
return static_cast<u8>(current << 4);
|
||||
}
|
||||
voltage -= MinimumSystemVoltageLimitMin;
|
||||
voltage /= 100;
|
||||
return static_cast<u8>(voltage << 1);
|
||||
|
||||
inline u32 DecodePreChargeCurrentLimit(u8 reg) {
|
||||
return PreChargeCurrentLimitMin + (static_cast<u32>(reg & 0xF0) << 3);
|
||||
}
|
||||
|
||||
constexpr u32 TerminationCurrentLimitMin = 128;
|
||||
constexpr u32 TerminationCurrentLimitMax = 2048;
|
||||
|
||||
inline u8 EncodeTerminationCurrentLimit(u32 current) {
|
||||
if (current < TerminationCurrentLimitMin || current > TerminationCurrentLimitMax) {
|
||||
std::abort();
|
||||
}
|
||||
current -= TerminationCurrentLimitMin;
|
||||
current >>= 7;
|
||||
return static_cast<u8>(current);
|
||||
}
|
||||
|
||||
inline u32 DecodeTerminationCurrentLimit(u8 reg) {
|
||||
return TerminationCurrentLimitMin + (static_cast<u32>(reg & 0xF) << 7);
|
||||
}
|
||||
|
||||
constexpr u32 MinimumSystemVoltageLimitMin = 3000;
|
||||
constexpr u32 MinimumSystemVoltageLimitMax = 3700;
|
||||
|
||||
inline u8 EncodeMinimumSystemVoltageLimit(u32 voltage) {
|
||||
if (voltage < MinimumSystemVoltageLimitMin || voltage > MinimumSystemVoltageLimitMax) {
|
||||
std::abort();
|
||||
}
|
||||
voltage -= MinimumSystemVoltageLimitMin;
|
||||
voltage /= 100;
|
||||
return static_cast<u8>(voltage << 1);
|
||||
}
|
||||
|
||||
inline u32 DecodeMinimumSystemVoltageLimit(u8 reg) {
|
||||
return MinimumSystemVoltageLimitMin + (static_cast<u32>(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,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static inline u32 DecodeMinimumSystemVoltageLimit(u8 reg) {
|
||||
return MinimumSystemVoltageLimitMin + (static_cast<u32>(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,
|
||||
};
|
@ -14,85 +14,95 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_calibration.hpp"
|
||||
|
||||
static constexpr size_t BatteryLotOffset = 0x2CE0;
|
||||
static constexpr size_t BatteryLotSize = 0x20;
|
||||
static constexpr size_t BatteryVersionOffset = 0x4310;
|
||||
static constexpr size_t BatteryVersionSize = 0x10;
|
||||
namespace sts::boot {
|
||||
|
||||
static constexpr u32 DefaultBatteryVendor = static_cast<u32>('A');
|
||||
static constexpr u32 DefaultBatteryVersion = 0;
|
||||
namespace {
|
||||
|
||||
static constexpr Result ResultCalInvalidCrc = 0xCAC6; /* TODO: Verify this really is cal, move to libstrat results. */
|
||||
/* Convenience definitions. */
|
||||
constexpr size_t BatteryLotOffset = 0x2CE0;
|
||||
constexpr size_t BatteryLotSize = 0x20;
|
||||
constexpr size_t BatteryVersionOffset = 0x4310;
|
||||
constexpr size_t BatteryVersionSize = 0x10;
|
||||
|
||||
u16 Boot::GetCrc16(const void *data, size_t size) {
|
||||
static constexpr u16 s_crc_table[0x10] = {
|
||||
0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
|
||||
0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
|
||||
};
|
||||
constexpr u32 DefaultBatteryVendor = static_cast<u32>('A');
|
||||
constexpr u32 DefaultBatteryVersion = 0;
|
||||
|
||||
constexpr Result ResultCalInvalidCrc = 0xCAC6; /* TODO: Verify this really is cal, move to libstrat results. */
|
||||
|
||||
/* Helpers. */
|
||||
constexpr u16 GetCrc16(const void *data, size_t size) {
|
||||
constexpr u16 s_crc_table[0x10] = {
|
||||
0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
|
||||
0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
|
||||
};
|
||||
|
||||
if (data == nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u16 crc16 = 0x55AA;
|
||||
const u8 *data_u8 = reinterpret_cast<const u8 *>(data);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
crc16 = (crc16 >> 4) ^ (s_crc_table[crc16 & 0xF]) ^ (s_crc_table[data_u8[i] & 0xF]);
|
||||
crc16 = (crc16 >> 4) ^ (s_crc_table[crc16 & 0xF]) ^ (s_crc_table[(data_u8[i] >> 4) & 0xF]);
|
||||
}
|
||||
return crc16;
|
||||
}
|
||||
|
||||
Result ValidateCalibrationCrc16(const void *data, size_t size) {
|
||||
const u8 *data_u8 = reinterpret_cast<const u8 *>(data);
|
||||
if (GetCrc16(data, size - sizeof(u16)) != *(reinterpret_cast<const u16 *>(&data_u8[size - sizeof(u16)]))) {
|
||||
return ResultCalInvalidCrc;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetBatteryVendorImpl(u32 *vendor) {
|
||||
FsStorage s;
|
||||
R_TRY(fsOpenBisStorage(&s, FsBisStorageId_CalibrationBinary));
|
||||
ON_SCOPE_EXIT { fsStorageClose(&s); };
|
||||
|
||||
u8 battery_lot[BatteryLotSize];
|
||||
R_TRY(fsStorageRead(&s, BatteryLotOffset, battery_lot, sizeof(battery_lot)));
|
||||
|
||||
R_TRY(ValidateCalibrationCrc16(battery_lot, sizeof(battery_lot)));
|
||||
|
||||
*vendor = battery_lot[7];
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result GetBatteryVersionImpl(u32 *version) {
|
||||
FsStorage s;
|
||||
R_TRY(fsOpenBisStorage(&s, FsBisStorageId_CalibrationBinary));
|
||||
ON_SCOPE_EXIT { fsStorageClose(&s); };
|
||||
|
||||
u8 battery_version[BatteryVersionSize];
|
||||
R_TRY(fsStorageRead(&s, BatteryVersionOffset, battery_version, sizeof(battery_version)));
|
||||
|
||||
R_TRY(ValidateCalibrationCrc16(battery_version, sizeof(battery_version)));
|
||||
|
||||
*version = battery_version[0];
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (data == nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u16 crc16 = 0x55AA;
|
||||
const u8 *data_u8 = reinterpret_cast<const u8 *>(data);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
crc16 = (crc16 >> 4) ^ (s_crc_table[crc16 & 0xF]) ^ (s_crc_table[data_u8[i] & 0xF]);
|
||||
crc16 = (crc16 >> 4) ^ (s_crc_table[crc16 & 0xF]) ^ (s_crc_table[(data_u8[i] >> 4) & 0xF]);
|
||||
u32 GetBatteryVendor() {
|
||||
u32 vendor;
|
||||
if (R_FAILED(GetBatteryVendorImpl(&vendor))) {
|
||||
return DefaultBatteryVendor;
|
||||
}
|
||||
return vendor;
|
||||
}
|
||||
return crc16;
|
||||
}
|
||||
|
||||
static Result ValidateCalibrationCrc16(const void *data, size_t size) {
|
||||
const u8 *data_u8 = reinterpret_cast<const u8 *>(data);
|
||||
if (Boot::GetCrc16(data, size - sizeof(u16)) != *(reinterpret_cast<const u16 *>(&data_u8[size - sizeof(u16)]))) {
|
||||
return ResultCalInvalidCrc;
|
||||
u32 GetBatteryVersion() {
|
||||
u32 version;
|
||||
if (R_FAILED(GetBatteryVersionImpl(&version))) {
|
||||
return DefaultBatteryVersion;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static Result GetBatteryVendorImpl(u32 *vendor) {
|
||||
FsStorage s;
|
||||
R_TRY(fsOpenBisStorage(&s, FsBisStorageId_CalibrationBinary));
|
||||
ON_SCOPE_EXIT { fsStorageClose(&s); };
|
||||
|
||||
u8 battery_lot[BatteryLotSize];
|
||||
R_TRY(fsStorageRead(&s, BatteryLotOffset, battery_lot, sizeof(battery_lot)));
|
||||
|
||||
R_TRY(ValidateCalibrationCrc16(battery_lot, sizeof(battery_lot)));
|
||||
|
||||
*vendor = battery_lot[7];
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static Result GetBatteryVersionImpl(u32 *version) {
|
||||
FsStorage s;
|
||||
R_TRY(fsOpenBisStorage(&s, FsBisStorageId_CalibrationBinary));
|
||||
ON_SCOPE_EXIT { fsStorageClose(&s); };
|
||||
|
||||
u8 battery_version[BatteryVersionSize];
|
||||
R_TRY(fsStorageRead(&s, BatteryVersionOffset, battery_version, sizeof(battery_version)));
|
||||
|
||||
R_TRY(ValidateCalibrationCrc16(battery_version, sizeof(battery_version)));
|
||||
|
||||
*version = battery_version[0];
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
u32 Boot::GetBatteryVendor() {
|
||||
u32 vendor;
|
||||
if (R_FAILED(GetBatteryVendorImpl(&vendor))) {
|
||||
return DefaultBatteryVendor;
|
||||
}
|
||||
return vendor;
|
||||
}
|
||||
|
||||
u32 Boot::GetBatteryVersion() {
|
||||
u32 version;
|
||||
if (R_FAILED(GetBatteryVersionImpl(&version))) {
|
||||
return DefaultBatteryVersion;
|
||||
}
|
||||
return version;
|
||||
|
||||
}
|
||||
|
27
stratosphere/boot/source/boot_calibration.hpp
Normal file
27
stratosphere/boot/source/boot_calibration.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
/* Calibration utilities. */
|
||||
u32 GetBatteryVersion();
|
||||
u32 GetBatteryVendor();
|
||||
|
||||
}
|
@ -14,23 +14,33 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_change_voltage.hpp"
|
||||
#include "boot_pmc_wrapper.hpp"
|
||||
|
||||
static constexpr u32 Sdmmc3VoltageBit = (1 << 13); /* SDMMC3 */
|
||||
static constexpr u32 AudioVoltageBit = (1 << 18); /* AUDIO_HV */
|
||||
static constexpr u32 GpioVoltageBit = (1 << 21); /* GPIO */
|
||||
static constexpr u32 SpiVoltageBit = (1 << 23); /* SPI_HV */
|
||||
namespace sts::boot {
|
||||
|
||||
static constexpr u32 VoltageChangeMask = SpiVoltageBit | GpioVoltageBit | AudioVoltageBit | Sdmmc3VoltageBit;
|
||||
namespace {
|
||||
|
||||
static constexpr u32 PmcPwrDet = 0x7000E448;
|
||||
static constexpr u32 PmcPwrDetVal = 0x7000E4E4;
|
||||
/* Convenience definitions. */
|
||||
constexpr u32 Sdmmc3VoltageBit = (1 << 13); /* SDMMC3 */
|
||||
constexpr u32 AudioVoltageBit = (1 << 18); /* AUDIO_HV */
|
||||
constexpr u32 GpioVoltageBit = (1 << 21); /* GPIO */
|
||||
constexpr u32 SpiVoltageBit = (1 << 23); /* SPI_HV */
|
||||
|
||||
void Boot::ChangeGpioVoltageTo1_8v() {
|
||||
/* Write mask to APBDEV_PMC_PWR_DET, then clear APBDEV_PMC_PWR_DET_VAL. */
|
||||
WritePmcRegister(PmcPwrDet, VoltageChangeMask, VoltageChangeMask);
|
||||
WritePmcRegister(PmcPwrDetVal, 0, VoltageChangeMask);
|
||||
constexpr u32 VoltageChangeMask = SpiVoltageBit | GpioVoltageBit | AudioVoltageBit | Sdmmc3VoltageBit;
|
||||
|
||||
constexpr u32 PmcPwrDet = 0x7000E448;
|
||||
constexpr u32 PmcPwrDetVal = 0x7000E4E4;
|
||||
|
||||
}
|
||||
|
||||
void ChangeGpioVoltageTo1_8v() {
|
||||
/* Write mask to APBDEV_PMC_PWR_DET, then clear APBDEV_PMC_PWR_DET_VAL. */
|
||||
WritePmcRegister(PmcPwrDet, VoltageChangeMask, VoltageChangeMask);
|
||||
WritePmcRegister(PmcPwrDetVal, 0, VoltageChangeMask);
|
||||
|
||||
/* Sleep for 100 us. */
|
||||
svcSleepThread(100'000ul);
|
||||
}
|
||||
|
||||
/* Sleep for 100 us. */
|
||||
svcSleepThread(100'000ul);
|
||||
}
|
||||
|
25
stratosphere/boot/source/boot_change_voltage.hpp
Normal file
25
stratosphere/boot/source/boot_change_voltage.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
void ChangeGpioVoltageTo1_8v();;
|
||||
|
||||
}
|
@ -16,116 +16,121 @@
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "boot_charger_driver.hpp"
|
||||
|
||||
Result ChargerDriver::Read(u8 addr, u8 *out) {
|
||||
return Boot::ReadI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(out), sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
namespace sts::boot {
|
||||
|
||||
Result ChargerDriver::Write(u8 addr, u8 val) {
|
||||
return Boot::WriteI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(&val), sizeof(val), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result ChargerDriver::ReadWrite(u8 addr, u8 mask, u8 val) {
|
||||
u8 cur_val;
|
||||
R_TRY(this->Read(addr, &cur_val));
|
||||
|
||||
const u8 new_val = (cur_val & ~mask) | val;
|
||||
R_TRY(this->Write(addr, new_val));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ChargerDriver::Initialize() {
|
||||
return this->Initialize(true);
|
||||
}
|
||||
|
||||
Result ChargerDriver::Initialize(bool set_input_current_limit) {
|
||||
if (set_input_current_limit) {
|
||||
R_TRY(this->SetInputCurrentLimit(InputCurrentLimit_500mA));
|
||||
Result ChargerDriver::Read(u8 addr, u8 *out) {
|
||||
return ReadI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(out), sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
R_TRY(this->SetChargeVoltageLimit(4208));
|
||||
R_TRY(this->SetFastChargeCurrentLimit(512));
|
||||
R_TRY(this->SetForce20PercentChargeCurrent(false));
|
||||
R_TRY(this->SetPreChargeCurrentLimit(128));
|
||||
R_TRY(this->SetTerminationCurrentLimit(128));
|
||||
R_TRY(this->SetMinimumSystemVoltageLimit(3000));
|
||||
R_TRY(this->SetWatchdogTimerSetting(WatchdogTimerSetting_Disabled));
|
||||
R_TRY(this->SetChargingSafetyTimerEnabled(false));
|
||||
R_TRY(this->ResetWatchdogTimer());
|
||||
R_TRY(this->SetBoostModeCurrentLimit(BoostModeCurrentLimit_500mA));
|
||||
R_TRY(this->SetHiZEnabled(false));
|
||||
Result ChargerDriver::Write(u8 addr, u8 val) {
|
||||
return WriteI2cRegister(this->i2c_session, reinterpret_cast<u8 *>(&val), sizeof(val), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
Result ChargerDriver::ReadWrite(u8 addr, u8 mask, u8 val) {
|
||||
u8 cur_val;
|
||||
R_TRY(this->Read(addr, &cur_val));
|
||||
|
||||
Result ChargerDriver::SetChargeEnabled(bool enabled) {
|
||||
Boot::GpioSetValue(GpioPadName_Bq24193Charger, enabled ? GpioValue_Low : GpioValue_High);
|
||||
return this->SetChargerConfiguration(ChargerConfiguration_ChargeBattery);
|
||||
}
|
||||
const u8 new_val = (cur_val & ~mask) | val;
|
||||
R_TRY(this->Write(addr, new_val));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetChargerConfiguration(ChargerConfiguration config) {
|
||||
return this->ReadWrite(Bq24193PowerOnConfiguration, 0x30, config);
|
||||
}
|
||||
Result ChargerDriver::Initialize() {
|
||||
return this->Initialize(true);
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetChargeVoltageLimit(u32 voltage) {
|
||||
return this->ReadWrite(Bq24193ChargeVoltageControl, 0xFC, EncodeChargeVoltageLimit(voltage));
|
||||
}
|
||||
Result ChargerDriver::Initialize(bool set_input_current_limit) {
|
||||
if (set_input_current_limit) {
|
||||
R_TRY(this->SetInputCurrentLimit(bq24193::InputCurrentLimit_500mA));
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetFastChargeCurrentLimit(u32 current) {
|
||||
return this->ReadWrite(Bq24193ChargeCurrentControl, 0xFC, EncodeFastChargeCurrentLimit(current));
|
||||
}
|
||||
R_TRY(this->SetChargeVoltageLimit(4208));
|
||||
R_TRY(this->SetFastChargeCurrentLimit(512));
|
||||
R_TRY(this->SetForce20PercentChargeCurrent(false));
|
||||
R_TRY(this->SetPreChargeCurrentLimit(128));
|
||||
R_TRY(this->SetTerminationCurrentLimit(128));
|
||||
R_TRY(this->SetMinimumSystemVoltageLimit(3000));
|
||||
R_TRY(this->SetWatchdogTimerSetting(bq24193::WatchdogTimerSetting_Disabled));
|
||||
R_TRY(this->SetChargingSafetyTimerEnabled(false));
|
||||
R_TRY(this->ResetWatchdogTimer());
|
||||
R_TRY(this->SetBoostModeCurrentLimit(bq24193::BoostModeCurrentLimit_500mA));
|
||||
R_TRY(this->SetHiZEnabled(false));
|
||||
|
||||
Result ChargerDriver::SetInputCurrentLimit(InputCurrentLimit current) {
|
||||
return this->ReadWrite(Bq24193InputSourceControl, 0x07, current);
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetForce20PercentChargeCurrent(bool force) {
|
||||
return this->ReadWrite(Bq24193ChargeCurrentControl, 0x01, force ? 1 : 0);
|
||||
}
|
||||
Result ChargerDriver::SetChargeEnabled(bool enabled) {
|
||||
gpio::SetValue(GpioPadName_Bq24193Charger, enabled ? GpioValue_Low : GpioValue_High);
|
||||
return this->SetChargerConfiguration(bq24193::ChargerConfiguration_ChargeBattery);
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetPreChargeCurrentLimit(u32 current) {
|
||||
return this->ReadWrite(Bq24193PreChargeTerminationCurrentControl, 0xF0, EncodePreChargeCurrentLimit(current));
|
||||
}
|
||||
Result ChargerDriver::SetChargerConfiguration(bq24193::ChargerConfiguration config) {
|
||||
return this->ReadWrite(bq24193::PowerOnConfiguration, 0x30, config);
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetTerminationCurrentLimit(u32 current) {
|
||||
return this->ReadWrite(Bq24193PreChargeTerminationCurrentControl, 0x0F, EncodeTerminationCurrentLimit(current));
|
||||
}
|
||||
Result ChargerDriver::SetChargeVoltageLimit(u32 voltage) {
|
||||
return this->ReadWrite(bq24193::ChargeVoltageControl, 0xFC, bq24193::EncodeChargeVoltageLimit(voltage));
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetMinimumSystemVoltageLimit(u32 voltage) {
|
||||
return this->ReadWrite(Bq24193PowerOnConfiguration, 0x0E, EncodeMinimumSystemVoltageLimit(voltage));
|
||||
}
|
||||
Result ChargerDriver::SetFastChargeCurrentLimit(u32 current) {
|
||||
return this->ReadWrite(bq24193::ChargeCurrentControl, 0xFC, bq24193::EncodeFastChargeCurrentLimit(current));
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetWatchdogTimerSetting(WatchdogTimerSetting setting) {
|
||||
return this->ReadWrite(Bq24193ChargeTerminationTimerControl, 0x30, setting);
|
||||
}
|
||||
Result ChargerDriver::SetInputCurrentLimit(bq24193::InputCurrentLimit current) {
|
||||
return this->ReadWrite(bq24193::InputSourceControl, 0x07, current);
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetChargingSafetyTimerEnabled(bool enabled) {
|
||||
return this->ReadWrite(Bq24193ChargeTerminationTimerControl, 0x08, enabled ? 0x08 : 0);
|
||||
}
|
||||
Result ChargerDriver::SetForce20PercentChargeCurrent(bool force) {
|
||||
return this->ReadWrite(bq24193::ChargeCurrentControl, 0x01, force ? 1 : 0);
|
||||
}
|
||||
|
||||
Result ChargerDriver::ResetWatchdogTimer() {
|
||||
return this->ReadWrite(Bq24193PowerOnConfiguration, 0x40, 0x40);
|
||||
}
|
||||
Result ChargerDriver::SetPreChargeCurrentLimit(u32 current) {
|
||||
return this->ReadWrite(bq24193::PreChargeTerminationCurrentControl, 0xF0, bq24193::EncodePreChargeCurrentLimit(current));
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetBoostModeCurrentLimit(BoostModeCurrentLimit current) {
|
||||
return this->ReadWrite(Bq24193PowerOnConfiguration, 0x01, current);
|
||||
}
|
||||
Result ChargerDriver::SetTerminationCurrentLimit(u32 current) {
|
||||
return this->ReadWrite(bq24193::PreChargeTerminationCurrentControl, 0x0F, bq24193::EncodeTerminationCurrentLimit(current));
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetHiZEnabled(bool enabled) {
|
||||
return this->ReadWrite(Bq24193InputSourceControl, 0x80, enabled ? 0x80 : 0);
|
||||
}
|
||||
Result ChargerDriver::SetMinimumSystemVoltageLimit(u32 voltage) {
|
||||
return this->ReadWrite(bq24193::PowerOnConfiguration, 0x0E, bq24193::EncodeMinimumSystemVoltageLimit(voltage));
|
||||
}
|
||||
|
||||
Result ChargerDriver::GetInputCurrentLimit(InputCurrentLimit *out) {
|
||||
u8 limit;
|
||||
R_TRY(this->Read(Bq24193InputSourceControl, &limit));
|
||||
*out = static_cast<InputCurrentLimit>(limit);
|
||||
return ResultSuccess;
|
||||
}
|
||||
Result ChargerDriver::SetWatchdogTimerSetting(bq24193::WatchdogTimerSetting setting) {
|
||||
return this->ReadWrite(bq24193::ChargeTerminationTimerControl, 0x30, setting);
|
||||
}
|
||||
|
||||
Result ChargerDriver::GetChargeVoltageLimit(u32 *out) {
|
||||
u8 reg;
|
||||
R_TRY(this->Read(Bq24193ChargeVoltageControl, ®));
|
||||
*out = DecodeChargeVoltageLimit(reg);
|
||||
return ResultSuccess;
|
||||
}
|
||||
Result ChargerDriver::SetChargingSafetyTimerEnabled(bool enabled) {
|
||||
return this->ReadWrite(bq24193::ChargeTerminationTimerControl, 0x08, enabled ? 0x08 : 0);
|
||||
}
|
||||
|
||||
Result ChargerDriver::ResetWatchdogTimer() {
|
||||
return this->ReadWrite(bq24193::PowerOnConfiguration, 0x40, 0x40);
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetBoostModeCurrentLimit(bq24193::BoostModeCurrentLimit current) {
|
||||
return this->ReadWrite(bq24193::PowerOnConfiguration, 0x01, current);
|
||||
}
|
||||
|
||||
Result ChargerDriver::SetHiZEnabled(bool enabled) {
|
||||
return this->ReadWrite(bq24193::InputSourceControl, 0x80, enabled ? 0x80 : 0);
|
||||
}
|
||||
|
||||
Result ChargerDriver::GetInputCurrentLimit(bq24193::InputCurrentLimit *out) {
|
||||
u8 limit;
|
||||
R_TRY(this->Read(bq24193::InputSourceControl, &limit));
|
||||
*out = static_cast<bq24193::InputCurrentLimit>(limit);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ChargerDriver::GetChargeVoltageLimit(u32 *out) {
|
||||
u8 reg;
|
||||
R_TRY(this->Read(bq24193::ChargeVoltageControl, ®));
|
||||
*out = bq24193::DecodeChargeVoltageLimit(reg);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
@ -18,51 +18,56 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_driver/i2c_api.hpp"
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_bq24193_charger.hpp"
|
||||
#include "boot_i2c_utils.hpp"
|
||||
|
||||
class ChargerDriver {
|
||||
private:
|
||||
static constexpr u32 GpioPadName_Bq24193Charger = 0xA;
|
||||
private:
|
||||
I2cSessionImpl i2c_session;
|
||||
public:
|
||||
ChargerDriver() {
|
||||
I2cDriver::Initialize();
|
||||
I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Bq24193);
|
||||
#include "gpio/gpio_utils.hpp"
|
||||
|
||||
Boot::GpioConfigure(GpioPadName_Bq24193Charger);
|
||||
Boot::GpioSetDirection(GpioPadName_Bq24193Charger, GpioDirection_Output);
|
||||
}
|
||||
namespace sts::boot {
|
||||
|
||||
~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);
|
||||
class ChargerDriver {
|
||||
private:
|
||||
static constexpr u32 GpioPadName_Bq24193Charger = 0xA;
|
||||
private:
|
||||
i2c::driver::Session i2c_session;
|
||||
public:
|
||||
ChargerDriver() {
|
||||
i2c::driver::Initialize();
|
||||
i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Bq24193);
|
||||
|
||||
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);
|
||||
gpio::Configure(GpioPadName_Bq24193Charger);
|
||||
gpio::SetDirection(GpioPadName_Bq24193Charger, GpioDirection_Output);
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
~ChargerDriver() {
|
||||
i2c::driver::CloseSession(this->i2c_session);
|
||||
i2c::driver::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(bq24193::InputCurrentLimit current);
|
||||
Result SetForce20PercentChargeCurrent(bool force);
|
||||
Result SetPreChargeCurrentLimit(u32 current);
|
||||
Result SetTerminationCurrentLimit(u32 current);
|
||||
Result SetMinimumSystemVoltageLimit(u32 voltage);
|
||||
Result SetWatchdogTimerSetting(bq24193::WatchdogTimerSetting setting);
|
||||
Result SetChargingSafetyTimerEnabled(bool enabled);
|
||||
Result ResetWatchdogTimer();
|
||||
Result SetBoostModeCurrentLimit(bq24193::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(bq24193::ChargerConfiguration config);
|
||||
Result GetInputCurrentLimit(bq24193::InputCurrentLimit *out);
|
||||
Result GetChargeVoltageLimit(u32 *out);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -14,262 +14,276 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_pmic_driver.hpp"
|
||||
#include "boot_battery_driver.hpp"
|
||||
#include "boot_battery_icons.hpp"
|
||||
#include "boot_boot_reason.hpp"
|
||||
#include "boot_calibration.hpp"
|
||||
#include "boot_charger_driver.hpp"
|
||||
#include "boot_check_battery.hpp"
|
||||
#include "boot_pmic_driver.hpp"
|
||||
#include "boot_power_utils.hpp"
|
||||
|
||||
enum CheckBatteryResult {
|
||||
CheckBatteryResult_Success = 0,
|
||||
CheckBatteryResult_Shutdown = 1,
|
||||
CheckBatteryResult_Reboot = 2,
|
||||
};
|
||||
namespace sts::boot {
|
||||
|
||||
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;
|
||||
};
|
||||
namespace {
|
||||
|
||||
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,
|
||||
};
|
||||
/* Types. */
|
||||
enum class CheckBatteryResult {
|
||||
Success,
|
||||
Shutdown,
|
||||
Reboot,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
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 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,
|
||||
};
|
||||
/* Battery parameters. */
|
||||
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 const BatteryChargeParameters *GetBatteryChargeParameters(u32 battery_version) {
|
||||
switch (battery_version) {
|
||||
case 0:
|
||||
return &BatteryChargeParameters0;
|
||||
case 1:
|
||||
return &BatteryChargeParameters1;
|
||||
case 2:
|
||||
return &BatteryChargeParameters2;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
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 void UpdateCharger(PmicDriver *pmic_driver, ChargerDriver *charger_driver, BatteryDriver *battery_driver, const BatteryChargeParameters *params, u32 charge_voltage_limit) {
|
||||
double temperature;
|
||||
u32 battery_voltage;
|
||||
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,
|
||||
};
|
||||
|
||||
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;
|
||||
constexpr const BatteryChargeParameters *GetBatteryChargeParameters(u32 battery_version) {
|
||||
switch (battery_version) {
|
||||
case 0:
|
||||
return &BatteryChargeParameters0;
|
||||
case 1:
|
||||
return &BatteryChargeParameters1;
|
||||
case 2:
|
||||
return &BatteryChargeParameters2;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (can_show_battery_icon && !is_showing_charging_icon) {
|
||||
Boot::StartShowChargingIcon(1, false);
|
||||
is_showing_charging_icon = true;
|
||||
/* Helpers. */
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
svcSleepThread(20'000'000ul);
|
||||
UpdateCharger(pmic_driver, charger_driver, battery_driver, params, charge_voltage_limit);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
void Boot::CheckBatteryCharge() {
|
||||
PmicDriver pmic_driver;
|
||||
BatteryDriver battery_driver;
|
||||
ChargerDriver charger_driver;
|
||||
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) {
|
||||
EndShowChargingIcon();
|
||||
}
|
||||
};
|
||||
|
||||
if (R_FAILED(battery_driver.InitializeBatteryParameters())) {
|
||||
pmic_driver.ShutdownSystem();
|
||||
if (can_show_charging_icon) {
|
||||
size_t battery_percentage;
|
||||
if (R_FAILED(battery_driver->GetBatteryPercentage(&battery_percentage))) {
|
||||
return CheckBatteryResult::Shutdown;
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
StartShowChargingIcon(1, false);
|
||||
is_showing_charging_icon = true;
|
||||
}
|
||||
|
||||
svcSleepThread(20'000'000ul);
|
||||
UpdateCharger(pmic_driver, charger_driver, battery_driver, params, charge_voltage_limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
bool removed;
|
||||
if (R_FAILED(battery_driver.IsBatteryRemoved(&removed)) || removed) {
|
||||
|
||||
void 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 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))) {
|
||||
const u32 boot_reason = GetBootReason();
|
||||
bq24193::InputCurrentLimit input_current_limit;
|
||||
if (R_FAILED(charger_driver.Initialize(boot_reason != 4)) || R_FAILED(charger_driver.GetInputCurrentLimit(&input_current_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);
|
||||
|
||||
if (input_current_limit <= bq24193::InputCurrentLimit_150mA) {
|
||||
charger_driver.SetChargerConfiguration(bq24193::ChargerConfiguration_ChargeDisable);
|
||||
pmic_driver.ShutdownSystem();
|
||||
}
|
||||
|
||||
const BatteryChargeParameters *params = GetBatteryChargeParameters(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 {
|
||||
check_result = LoopCheckBattery(&pmic_driver, &charger_driver, &battery_driver, params, charge_voltage_limit, false, true, false, true, false);
|
||||
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:
|
||||
break;
|
||||
case CheckBatteryResult::Shutdown:
|
||||
pmic_driver.ShutdownSystem();
|
||||
break;
|
||||
case CheckBatteryResult::Reboot:
|
||||
RebootSystem();
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
switch (check_result) {
|
||||
case CheckBatteryResult_Success:
|
||||
break;
|
||||
case CheckBatteryResult_Shutdown:
|
||||
pmic_driver.ShutdownSystem();
|
||||
break;
|
||||
case CheckBatteryResult_Reboot:
|
||||
Boot::RebootSystem();
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
25
stratosphere/boot/source/boot_check_battery.hpp
Normal file
25
stratosphere/boot/source/boot_check_battery.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
void CheckBatteryCharge();
|
||||
|
||||
}
|
@ -14,35 +14,43 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include <stratosphere/reg.hpp>
|
||||
#include "boot_check_clock.hpp"
|
||||
#include "boot_power_utils.hpp"
|
||||
|
||||
static constexpr u32 ExpectedPlluDivP = (1 << 16);
|
||||
static constexpr u32 ExpectedPlluDivN = (25 << 8);
|
||||
static constexpr u32 ExpectedPlluDivM = (2 << 0);
|
||||
static constexpr u32 ExpectedPlluVal = (ExpectedPlluDivP | ExpectedPlluDivN | ExpectedPlluDivM);
|
||||
static constexpr u32 ExpectedPlluMask = 0x1FFFFF;
|
||||
namespace sts::boot {
|
||||
|
||||
static constexpr u32 ExpectedUtmipDivN = (25 << 16);
|
||||
static constexpr u32 ExpectedUtmipDivM = (1 << 8);
|
||||
static constexpr u32 ExpectedUtmipVal = (ExpectedUtmipDivN | ExpectedUtmipDivM);
|
||||
static constexpr u32 ExpectedUtmipMask = 0xFFFF00;
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr u32 ExpectedPlluDivP = (1 << 16);
|
||||
constexpr u32 ExpectedPlluDivN = (25 << 8);
|
||||
constexpr u32 ExpectedPlluDivM = (2 << 0);
|
||||
constexpr u32 ExpectedPlluVal = (ExpectedPlluDivP | ExpectedPlluDivN | ExpectedPlluDivM);
|
||||
constexpr u32 ExpectedPlluMask = 0x1FFFFF;
|
||||
|
||||
constexpr u32 ExpectedUtmipDivN = (25 << 16);
|
||||
constexpr u32 ExpectedUtmipDivM = (1 << 8);
|
||||
constexpr u32 ExpectedUtmipVal = (ExpectedUtmipDivN | ExpectedUtmipDivM);
|
||||
constexpr u32 ExpectedUtmipMask = 0xFFFF00;
|
||||
|
||||
/* Helpers. */
|
||||
bool IsUsbClockValid() {
|
||||
uintptr_t car_regs = GetIoMapping(0x60006000ul, 0x1000);
|
||||
|
||||
const u32 pllu = reg::Read(car_regs + 0xC0);
|
||||
const u32 utmip = reg::Read(car_regs + 0x480);
|
||||
return ((pllu & ExpectedPlluMask) == ExpectedPlluVal) && ((utmip & ExpectedUtmipMask) == ExpectedUtmipVal);
|
||||
}
|
||||
|
||||
static bool IsUsbClockValid() {
|
||||
u64 _vaddr;
|
||||
if (R_FAILED(svcQueryIoMapping(&_vaddr, 0x60006000ul, 0x1000))) {
|
||||
std::abort();
|
||||
}
|
||||
volatile u32 *car_regs = reinterpret_cast<volatile u32 *>(_vaddr);
|
||||
|
||||
const u32 pllu = car_regs[0xC0 >> 2];
|
||||
const u32 utmip = car_regs[0x480 >> 2];
|
||||
return ((pllu & ExpectedPlluMask) == ExpectedPlluVal) && ((utmip & ExpectedUtmipMask) == ExpectedUtmipVal);
|
||||
}
|
||||
|
||||
void Boot::CheckClock() {
|
||||
if (!IsUsbClockValid()) {
|
||||
/* Sleep for 1s, then reboot. */
|
||||
svcSleepThread(1'000'000'000ul);
|
||||
Boot::RebootSystem();
|
||||
void CheckClock() {
|
||||
if (!IsUsbClockValid()) {
|
||||
/* Sleep for 1s, then reboot. */
|
||||
svcSleepThread(1'000'000'000ul);
|
||||
RebootSystem();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
25
stratosphere/boot/source/boot_check_clock.hpp
Normal file
25
stratosphere/boot/source/boot_check_clock.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
void CheckClock();
|
||||
|
||||
}
|
@ -14,15 +14,25 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_clock_initial_configuration.hpp"
|
||||
#include "boot_pmc_wrapper.hpp"
|
||||
#include "boot_registers_pmc.hpp"
|
||||
|
||||
static constexpr u32 PmcClkOutCntrl = PmcBase + APBDEV_PMC_CLK_OUT_CNTRL;
|
||||
static constexpr u32 InitialClockOutMask1x = 0x00C4;
|
||||
static constexpr u32 InitialClockOutMask6x = 0xC4C4;
|
||||
namespace sts::boot {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr u32 PmcClkOutCntrl = PmcBase + APBDEV_PMC_CLK_OUT_CNTRL;
|
||||
constexpr u32 InitialClockOutMask1x = 0x00C4;
|
||||
constexpr u32 InitialClockOutMask6x = 0xC4C4;
|
||||
|
||||
}
|
||||
|
||||
void SetInitialClockConfiguration() {
|
||||
/* Write mask to APBDEV_PMC_PWR_DET, then clear APBDEV_PMC_PWR_DET_VAL. */
|
||||
const u32 mask = GetRuntimeFirmwareVersion() >= FirmwareVersion_600 ? InitialClockOutMask6x : InitialClockOutMask1x;
|
||||
WritePmcRegister(PmcClkOutCntrl, mask, mask);
|
||||
}
|
||||
|
||||
void Boot::SetInitialClockConfiguration() {
|
||||
/* Write mask to APBDEV_PMC_PWR_DET, then clear APBDEV_PMC_PWR_DET_VAL. */
|
||||
const u32 mask = GetRuntimeFirmwareVersion() >= FirmwareVersion_600 ? InitialClockOutMask6x : InitialClockOutMask1x;
|
||||
WritePmcRegister(PmcClkOutCntrl, mask, mask);
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
void SetInitialClockConfiguration();
|
||||
|
||||
}
|
@ -14,523 +14,497 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_display_config.hpp"
|
||||
#include "i2c_driver/i2c_api.hpp"
|
||||
#include <stratosphere/reg.hpp>
|
||||
#include <stratosphere/spl.hpp>
|
||||
|
||||
/* Helpful defines. */
|
||||
constexpr size_t DeviceAddressSpaceAlignSize = 0x400000;
|
||||
constexpr size_t DeviceAddressSpaceAlignMask = DeviceAddressSpaceAlignSize - 1;
|
||||
constexpr uintptr_t FrameBufferPaddr = DisplayConfigFrameBufferAddress;
|
||||
constexpr size_t FrameBufferWidth = 768;
|
||||
constexpr size_t FrameBufferHeight = 1280;
|
||||
constexpr size_t FrameBufferSize = FrameBufferHeight * FrameBufferWidth * sizeof(u32);
|
||||
#include "boot_display.hpp"
|
||||
#include "boot_i2c_utils.hpp"
|
||||
#include "boot_pmc_wrapper.hpp"
|
||||
|
||||
constexpr uintptr_t Disp1Base = 0x54200000ul;
|
||||
constexpr uintptr_t DsiBase = 0x54300000ul;
|
||||
constexpr uintptr_t ClkRstBase = 0x60006000ul;
|
||||
constexpr uintptr_t GpioBase = 0x6000D000ul;
|
||||
constexpr uintptr_t ApbMiscBase = 0x70000000ul;
|
||||
constexpr uintptr_t MipiCalBase = 0x700E3000ul;
|
||||
constexpr size_t Disp1Size = 0x3000;
|
||||
constexpr size_t DsiSize = 0x1000;
|
||||
constexpr size_t ClkRstSize = 0x1000;
|
||||
constexpr size_t GpioSize = 0x1000;
|
||||
constexpr size_t ApbMiscSize = 0x1000;
|
||||
constexpr size_t MipiCalSize = 0x1000;
|
||||
#include "boot_registers_clkrst.hpp"
|
||||
#include "boot_registers_di.hpp"
|
||||
#include "boot_registers_gpio.hpp"
|
||||
#include "boot_registers_pinmux.hpp"
|
||||
#include "boot_registers_pmc.hpp"
|
||||
|
||||
/* Types. */
|
||||
namespace sts::boot {
|
||||
|
||||
/* Globals. */
|
||||
static bool g_is_display_intialized = false;
|
||||
static u32 *g_frame_buffer = nullptr;
|
||||
static bool g_is_mariko = false;
|
||||
static u32 g_lcd_vendor = 0;
|
||||
static Handle g_dc_das_hnd = INVALID_HANDLE;
|
||||
static u8 g_frame_buffer_storage[DeviceAddressSpaceAlignSize + FrameBufferSize];
|
||||
/* Display configuration included into anonymous namespace. */
|
||||
namespace {
|
||||
|
||||
static uintptr_t g_disp1_regs = 0;
|
||||
static uintptr_t g_dsi_regs = 0;
|
||||
static uintptr_t g_clk_rst_regs = 0;
|
||||
static uintptr_t g_gpio_regs = 0;
|
||||
static uintptr_t g_apb_misc_regs = 0;
|
||||
static uintptr_t g_mipi_cal_regs = 0;
|
||||
#include "boot_display_config.inc"
|
||||
|
||||
static inline uintptr_t QueryVirtualAddress(uintptr_t phys, size_t size) {
|
||||
uintptr_t aligned_phys = phys & ~0xFFFul;
|
||||
size_t aligned_size = size + (phys - aligned_phys);
|
||||
uintptr_t aligned_virt;
|
||||
if (R_FAILED(svcQueryIoMapping(&aligned_virt, aligned_phys, aligned_size))) {
|
||||
std::abort();
|
||||
}
|
||||
return aligned_virt + (phys - aligned_phys);
|
||||
}
|
||||
|
||||
static inline void WriteRegister(volatile u32 *reg, u32 val) {
|
||||
*reg = val;
|
||||
}
|
||||
namespace {
|
||||
|
||||
static inline void WriteRegister(uintptr_t reg, u32 val) {
|
||||
WriteRegister(reinterpret_cast<volatile u32 *>(reg), val);
|
||||
}
|
||||
/* Helpful defines. */
|
||||
constexpr size_t DeviceAddressSpaceAlignSize = 0x400000;
|
||||
constexpr size_t DeviceAddressSpaceAlignMask = DeviceAddressSpaceAlignSize - 1;
|
||||
constexpr uintptr_t FrameBufferPaddr = DisplayConfigFrameBufferAddress;
|
||||
constexpr size_t FrameBufferWidth = 768;
|
||||
constexpr size_t FrameBufferHeight = 1280;
|
||||
constexpr size_t FrameBufferSize = FrameBufferHeight * FrameBufferWidth * sizeof(u32);
|
||||
|
||||
static inline u32 ReadRegister(volatile u32 *reg) {
|
||||
u32 val = *reg;
|
||||
return val;
|
||||
}
|
||||
constexpr uintptr_t Disp1Base = 0x54200000ul;
|
||||
constexpr uintptr_t DsiBase = 0x54300000ul;
|
||||
constexpr uintptr_t ClkRstBase = 0x60006000ul;
|
||||
constexpr uintptr_t GpioBase = 0x6000D000ul;
|
||||
constexpr uintptr_t ApbMiscBase = 0x70000000ul;
|
||||
constexpr uintptr_t MipiCalBase = 0x700E3000ul;
|
||||
constexpr size_t Disp1Size = 0x3000;
|
||||
constexpr size_t DsiSize = 0x1000;
|
||||
constexpr size_t ClkRstSize = 0x1000;
|
||||
constexpr size_t GpioSize = 0x1000;
|
||||
constexpr size_t ApbMiscSize = 0x1000;
|
||||
constexpr size_t MipiCalSize = 0x1000;
|
||||
|
||||
static inline u32 ReadRegister(uintptr_t reg) {
|
||||
return ReadRegister(reinterpret_cast<volatile u32 *>(reg));
|
||||
}
|
||||
/* Types. */
|
||||
|
||||
static inline void SetRegisterBits(volatile u32 *reg, u32 mask) {
|
||||
*reg |= mask;
|
||||
}
|
||||
/* Globals. */
|
||||
bool g_is_display_intialized = false;
|
||||
u32 *g_frame_buffer = nullptr;
|
||||
bool g_is_mariko = false;
|
||||
u32 g_lcd_vendor = 0;
|
||||
Handle g_dc_das_hnd = INVALID_HANDLE;
|
||||
u8 g_frame_buffer_storage[DeviceAddressSpaceAlignSize + FrameBufferSize];
|
||||
|
||||
static inline void SetRegisterBits(uintptr_t reg, u32 mask) {
|
||||
SetRegisterBits(reinterpret_cast<volatile u32 *>(reg), mask);
|
||||
}
|
||||
uintptr_t g_disp1_regs = 0;
|
||||
uintptr_t g_dsi_regs = 0;
|
||||
uintptr_t g_clk_rst_regs = 0;
|
||||
uintptr_t g_gpio_regs = 0;
|
||||
uintptr_t g_apb_misc_regs = 0;
|
||||
uintptr_t g_mipi_cal_regs = 0;
|
||||
|
||||
static inline void ClearRegisterBits(volatile u32 *reg, u32 mask) {
|
||||
*reg &= mask;
|
||||
}
|
||||
|
||||
static inline void ClearRegisterBits(uintptr_t reg, u32 mask) {
|
||||
ClearRegisterBits(reinterpret_cast<volatile u32 *>(reg), mask);
|
||||
}
|
||||
|
||||
static inline void ReadWriteRegisterBits(volatile u32 *reg, u32 val, u32 mask) {
|
||||
*reg = (*reg & (~mask)) | (val & mask);
|
||||
}
|
||||
|
||||
static inline void ReadWriteRegisterBits(uintptr_t reg, u32 val, u32 mask) {
|
||||
ReadWriteRegisterBits(reinterpret_cast<volatile u32 *>(reg), val, mask);
|
||||
}
|
||||
|
||||
static void InitializeRegisterBaseAddresses() {
|
||||
g_disp1_regs = QueryVirtualAddress(Disp1Base, Disp1Size);
|
||||
g_dsi_regs = QueryVirtualAddress(DsiBase, DsiSize);
|
||||
g_clk_rst_regs = QueryVirtualAddress(ClkRstBase, ClkRstSize);
|
||||
g_gpio_regs = QueryVirtualAddress(GpioBase, GpioSize);
|
||||
g_apb_misc_regs = QueryVirtualAddress(ApbMiscBase, ApbMiscSize);
|
||||
g_mipi_cal_regs = QueryVirtualAddress(MipiCalBase, MipiCalSize);
|
||||
}
|
||||
|
||||
static inline void DoRegisterWrites(uintptr_t base_address, const RegisterWrite *reg_writes, size_t num_writes) {
|
||||
for (size_t i = 0; i < num_writes; i++) {
|
||||
*(reinterpret_cast<volatile u32 *>(base_address + reg_writes[i].offset)) = reg_writes[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void DoSocDependentRegisterWrites(uintptr_t base_address, const RegisterWrite *reg_writes_erista, size_t num_writes_erista, const RegisterWrite *reg_writes_mariko, size_t num_writes_mariko) {
|
||||
if (g_is_mariko) {
|
||||
DoRegisterWrites(base_address, reg_writes_mariko, num_writes_mariko);
|
||||
} else {
|
||||
DoRegisterWrites(base_address, reg_writes_erista, num_writes_erista);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void DoDsiSleepOrRegisterWrites(const DsiSleepOrRegisterWrite *reg_writes, size_t num_writes) {
|
||||
for (size_t i = 0; i < num_writes; i++) {
|
||||
if (reg_writes[i].kind == DsiSleepOrRegisterWriteKind_Write) {
|
||||
*(reinterpret_cast<volatile u32 *>(g_dsi_regs + sizeof(u32) * reg_writes[i].offset)) = reg_writes[i].value;
|
||||
} else if (reg_writes[i].kind == DsiSleepOrRegisterWriteKind_Sleep) {
|
||||
svcSleepThread(1'000'000ul * u64(reg_writes[i].offset));
|
||||
} else {
|
||||
std::abort();
|
||||
/* Helper functions. */
|
||||
void InitializeRegisterBaseAddresses() {
|
||||
g_disp1_regs = GetIoMapping(Disp1Base, Disp1Size);
|
||||
g_dsi_regs = GetIoMapping(DsiBase, DsiSize);
|
||||
g_clk_rst_regs = GetIoMapping(ClkRstBase, ClkRstSize);
|
||||
g_gpio_regs = GetIoMapping(GpioBase, GpioSize);
|
||||
g_apb_misc_regs = GetIoMapping(ApbMiscBase, ApbMiscSize);
|
||||
g_mipi_cal_regs = GetIoMapping(MipiCalBase, MipiCalSize);
|
||||
}
|
||||
|
||||
inline void DoRegisterWrites(uintptr_t base_address, const RegisterWrite *reg_writes, size_t num_writes) {
|
||||
for (size_t i = 0; i < num_writes; i++) {
|
||||
reg::Write(base_address + reg_writes[i].offset, reg_writes[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
inline void DoSocDependentRegisterWrites(uintptr_t base_address, const RegisterWrite *reg_writes_erista, size_t num_writes_erista, const RegisterWrite *reg_writes_mariko, size_t num_writes_mariko) {
|
||||
if (g_is_mariko) {
|
||||
DoRegisterWrites(base_address, reg_writes_mariko, num_writes_mariko);
|
||||
} else {
|
||||
DoRegisterWrites(base_address, reg_writes_erista, num_writes_erista);
|
||||
}
|
||||
}
|
||||
|
||||
inline void DoDsiSleepOrRegisterWrites(const DsiSleepOrRegisterWrite *reg_writes, size_t num_writes) {
|
||||
for (size_t i = 0; i < num_writes; i++) {
|
||||
if (reg_writes[i].kind == DsiSleepOrRegisterWriteKind_Write) {
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * reg_writes[i].offset, reg_writes[i].value);
|
||||
} else if (reg_writes[i].kind == DsiSleepOrRegisterWriteKind_Sleep) {
|
||||
svcSleepThread(1'000'000ul * u64(reg_writes[i].offset));
|
||||
} else {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define DO_REGISTER_WRITES(base_address, writes) DoRegisterWrites(base_address, writes, sizeof(writes) / sizeof(writes[0]))
|
||||
#define DO_SOC_DEPENDENT_REGISTER_WRITES(base_address, writes) DoSocDependentRegisterWrites(base_address, writes##Erista, sizeof(writes##Erista) / sizeof(writes##Erista[0]), writes##Mariko, sizeof(writes##Mariko) / sizeof(writes##Mariko[0]))
|
||||
#define DO_DSI_SLEEP_OR_REGISTER_WRITES(writes) DoDsiSleepOrRegisterWrites(writes, sizeof(writes) / sizeof(writes[0]))
|
||||
|
||||
static void InitializeFrameBuffer() {
|
||||
if (g_frame_buffer != nullptr) {
|
||||
std::memset(g_frame_buffer, 0x00, FrameBufferSize);
|
||||
armDCacheFlush(g_frame_buffer, FrameBufferSize);
|
||||
} else {
|
||||
const uintptr_t frame_buffer_aligned = ((reinterpret_cast<uintptr_t>(g_frame_buffer_storage) + DeviceAddressSpaceAlignMask) & ~uintptr_t(DeviceAddressSpaceAlignMask));
|
||||
g_frame_buffer = reinterpret_cast<u32 *>(frame_buffer_aligned);
|
||||
std::memset(g_frame_buffer, 0x00, FrameBufferSize);
|
||||
armDCacheFlush(g_frame_buffer, FrameBufferSize);
|
||||
void InitializeFrameBuffer() {
|
||||
if (g_frame_buffer != nullptr) {
|
||||
std::memset(g_frame_buffer, 0x00, FrameBufferSize);
|
||||
armDCacheFlush(g_frame_buffer, FrameBufferSize);
|
||||
} else {
|
||||
const uintptr_t frame_buffer_aligned = ((reinterpret_cast<uintptr_t>(g_frame_buffer_storage) + DeviceAddressSpaceAlignMask) & ~uintptr_t(DeviceAddressSpaceAlignMask));
|
||||
g_frame_buffer = reinterpret_cast<u32 *>(frame_buffer_aligned);
|
||||
std::memset(g_frame_buffer, 0x00, FrameBufferSize);
|
||||
armDCacheFlush(g_frame_buffer, FrameBufferSize);
|
||||
|
||||
constexpr u64 DeviceName_DC = 2;
|
||||
constexpr u64 DeviceName_DC = 2;
|
||||
|
||||
/* Create Address Space. */
|
||||
if (R_FAILED(svcCreateDeviceAddressSpace(&g_dc_das_hnd, 0, (1ul << 32)))) {
|
||||
std::abort();
|
||||
}
|
||||
/* Attach it to the DC. */
|
||||
if (R_FAILED(svcAttachDeviceAddressSpace(DeviceName_DC, g_dc_das_hnd))) {
|
||||
std::abort();
|
||||
}
|
||||
/* Create Address Space. */
|
||||
if (R_FAILED(svcCreateDeviceAddressSpace(&g_dc_das_hnd, 0, (1ul << 32)))) {
|
||||
std::abort();
|
||||
}
|
||||
/* Attach it to the DC. */
|
||||
if (R_FAILED(svcAttachDeviceAddressSpace(DeviceName_DC, g_dc_das_hnd))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Map the framebuffer for the DC as read-only. */
|
||||
if (R_FAILED(svcMapDeviceAddressSpaceAligned(g_dc_das_hnd, CUR_PROCESS_HANDLE, frame_buffer_aligned, FrameBufferSize, FrameBufferPaddr, 1))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FinalizeFrameBuffer() {
|
||||
if (g_frame_buffer != nullptr) {
|
||||
const uintptr_t frame_buffer_aligned = reinterpret_cast<uintptr_t>(g_frame_buffer);
|
||||
constexpr u64 DeviceName_DC = 2;
|
||||
|
||||
/* Unmap the framebuffer from the DC. */
|
||||
if (R_FAILED(svcUnmapDeviceAddressSpace(g_dc_das_hnd, CUR_PROCESS_HANDLE, frame_buffer_aligned, FrameBufferSize, FrameBufferPaddr))) {
|
||||
std::abort();
|
||||
}
|
||||
/* Detach address space from the DC. */
|
||||
if (R_FAILED(svcDetachDeviceAddressSpace(DeviceName_DC, g_dc_das_hnd))) {
|
||||
std::abort();
|
||||
}
|
||||
/* Close the address space. */
|
||||
if (R_FAILED(svcCloseHandle(g_dc_das_hnd))) {
|
||||
std::abort();
|
||||
}
|
||||
g_dc_das_hnd = INVALID_HANDLE;
|
||||
g_frame_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void WaitDsiTrigger() {
|
||||
TimeoutHelper timeout_helper(250'000'000ul);
|
||||
|
||||
while (true) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
break;
|
||||
}
|
||||
if (ReadRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
svcSleepThread(5'000'000ul);
|
||||
}
|
||||
|
||||
static void WaitDsiHostControl() {
|
||||
TimeoutHelper timeout_helper(150'000'000ul);
|
||||
|
||||
while (true) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
break;
|
||||
}
|
||||
if ((ReadRegister(g_dsi_regs + sizeof(u32) * DSI_HOST_CONTROL) & DSI_HOST_CONTROL_IMM_BTA) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Boot::InitializeDisplay() {
|
||||
/* Setup globals. */
|
||||
InitializeRegisterBaseAddresses();
|
||||
g_is_mariko = Boot::IsMariko();
|
||||
InitializeFrameBuffer();
|
||||
|
||||
/* Turn on DSI/voltage rail. */
|
||||
{
|
||||
I2cSessionImpl i2c_session;
|
||||
I2cDriver::Initialize();
|
||||
I2cDriver::OpenSession(&i2c_session, I2cDevice_Max77620Pmic);
|
||||
|
||||
if (g_is_mariko) {
|
||||
Boot::WriteI2cRegister(i2c_session, 0x18, 0x3A);
|
||||
Boot::WriteI2cRegister(i2c_session, 0x1F, 0x71);
|
||||
}
|
||||
Boot::WriteI2cRegister(i2c_session, 0x23, 0xD0);
|
||||
|
||||
I2cDriver::Finalize();
|
||||
}
|
||||
|
||||
/* Enable MIPI CAL, DSI, DISP1, HOST1X, UART_FST_MIPI_CAL, DSIA LP clocks. */
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_H_CLR, 0x1010000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_H_SET, 0x1010000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_L_CLR, 0x18000000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_L_SET, 0x18000000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_X_SET, 0x20000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_SOURCE_UART_FST_MIPI_CAL, 0xA);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_W_SET, 0x80000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_SOURCE_DSIA_LP, 0xA);
|
||||
|
||||
/* DPD idle. */
|
||||
WritePmcRegister(PmcBase + APBDEV_PMC_IO_DPD_REQ, 0x40000000);
|
||||
WritePmcRegister(PmcBase + APBDEV_PMC_IO_DPD2_REQ, 0x40000000);
|
||||
|
||||
/* Configure LCD pinmux tristate + passthrough. */
|
||||
ClearRegisterBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_NFC_EN, ~PINMUX_TRISTATE);
|
||||
ClearRegisterBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_NFC_INT, ~PINMUX_TRISTATE);
|
||||
ClearRegisterBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, ~PINMUX_TRISTATE);
|
||||
ClearRegisterBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_EN, ~PINMUX_TRISTATE);
|
||||
ClearRegisterBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_RST, ~PINMUX_TRISTATE);
|
||||
|
||||
/* Configure LCD power, VDD. */
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT3_CNF_0, 0x3);
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT3_OE_0, 0x3);
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x1);
|
||||
svcSleepThread(10'000'000ul);
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x2);
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Configure LCD backlight. */
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x7);
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT6_OE_1, 0x7);
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x2);
|
||||
|
||||
/* Configure display interface and display. */
|
||||
WriteRegister(g_mipi_cal_regs + 0x060, 0);
|
||||
if (g_is_mariko) {
|
||||
WriteRegister(g_mipi_cal_regs + 0x058, 0);
|
||||
WriteRegister(g_apb_misc_regs + 0xAC0, 0);
|
||||
}
|
||||
|
||||
/* Execute configs. */
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld01);
|
||||
DO_REGISTER_WRITES(g_disp1_regs, DisplayConfigDc01);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init01);
|
||||
/* NOTE: Nintendo bug here. */
|
||||
/* As of 8.0.0, Nintendo writes this list to CAR instead of DSI */
|
||||
/* This results in them zeroing CLK_SOURCE_UARTA... */
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init02);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init03);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init04);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init05);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init06);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init07);
|
||||
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Enable backlight reset. */
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x4);
|
||||
svcSleepThread(60'000'000ul);
|
||||
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_BTA_TIMING, 0x50204);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x337);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
WaitDsiTrigger();
|
||||
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x406);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
WaitDsiTrigger();
|
||||
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_TX_TRIG_HOST | DSI_HOST_CONTROL_IMM_BTA | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC);
|
||||
WaitDsiHostControl();
|
||||
svcSleepThread(5'000'000ul);
|
||||
|
||||
/* Parse LCD vendor. */
|
||||
{
|
||||
u32 host_response[3];
|
||||
for (size_t i = 0; i < sizeof(host_response) / sizeof(host_response[0]); i++) {
|
||||
host_response[i] = ReadRegister(g_dsi_regs + sizeof(u32) * DSI_RD_DATA);
|
||||
}
|
||||
|
||||
if ((host_response[2] & 0xFF) == 0x10) {
|
||||
g_lcd_vendor = 0;
|
||||
} else {
|
||||
g_lcd_vendor = (host_response[2] >> 8) & 0xFF00;
|
||||
}
|
||||
g_lcd_vendor = (g_lcd_vendor & 0xFFFFFF00) | (host_response[2] & 0xFF);
|
||||
}
|
||||
|
||||
/* LCD vendor specific configuration. */
|
||||
switch (g_lcd_vendor) {
|
||||
case 0xF30: /* TODO: What's this? */
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(180'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x739);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x711148B1);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x143209);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
break;
|
||||
case 0xF20: /* TODO: What's this? */
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(180'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x739);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x751548B1);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x143209);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
break;
|
||||
case 0x10: /* Japan Display Inc screens. */
|
||||
DO_DSI_SLEEP_OR_REGISTER_WRITES(DisplayConfigJdiSpecificInit01);
|
||||
break;
|
||||
default:
|
||||
if ((g_lcd_vendor | 0x10) == 0x1030) {
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(120'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
/* Map the framebuffer for the DC as read-only. */
|
||||
if (R_FAILED(svcMapDeviceAddressSpaceAligned(g_dc_das_hnd, CUR_PROCESS_HANDLE, frame_buffer_aligned, FrameBufferSize, FrameBufferPaddr, 1))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
void FinalizeFrameBuffer() {
|
||||
if (g_frame_buffer != nullptr) {
|
||||
const uintptr_t frame_buffer_aligned = reinterpret_cast<uintptr_t>(g_frame_buffer);
|
||||
constexpr u64 DeviceName_DC = 2;
|
||||
|
||||
/* Unmap the framebuffer from the DC. */
|
||||
if (R_FAILED(svcUnmapDeviceAddressSpace(g_dc_das_hnd, CUR_PROCESS_HANDLE, frame_buffer_aligned, FrameBufferSize, FrameBufferPaddr))) {
|
||||
std::abort();
|
||||
}
|
||||
/* Detach address space from the DC. */
|
||||
if (R_FAILED(svcDetachDeviceAddressSpace(DeviceName_DC, g_dc_das_hnd))) {
|
||||
std::abort();
|
||||
}
|
||||
/* Close the address space. */
|
||||
if (R_FAILED(svcCloseHandle(g_dc_das_hnd))) {
|
||||
std::abort();
|
||||
}
|
||||
g_dc_das_hnd = INVALID_HANDLE;
|
||||
g_frame_buffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitDsiTrigger() {
|
||||
TimeoutHelper timeout_helper(250'000'000ul);
|
||||
|
||||
while (true) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
break;
|
||||
}
|
||||
if (reg::Read(g_dsi_regs + sizeof(u32) * DSI_TRIGGER) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
svcSleepThread(5'000'000ul);
|
||||
}
|
||||
|
||||
void WaitDsiHostControl() {
|
||||
TimeoutHelper timeout_helper(150'000'000ul);
|
||||
|
||||
while (true) {
|
||||
if (timeout_helper.TimedOut()) {
|
||||
break;
|
||||
}
|
||||
if ((reg::Read(g_dsi_regs + sizeof(u32) * DSI_HOST_CONTROL) & DSI_HOST_CONTROL_IMM_BTA) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
svcSleepThread(20'000'000ul);
|
||||
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld02);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init08);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init09);
|
||||
void InitializeDisplay() {
|
||||
/* Setup globals. */
|
||||
InitializeRegisterBaseAddresses();
|
||||
g_is_mariko = spl::IsMariko();
|
||||
InitializeFrameBuffer();
|
||||
|
||||
WriteRegister(g_disp1_regs + sizeof(u32) * DC_DISP_DISP_CLOCK_CONTROL, SHIFT_CLK_DIVIDER(4));
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init10);
|
||||
svcSleepThread(10'000'000ul);
|
||||
/* Turn on DSI/voltage rail. */
|
||||
{
|
||||
i2c::driver::Session i2c_session;
|
||||
i2c::driver::Initialize();
|
||||
ON_SCOPE_EXIT { i2c::driver::Finalize(); };
|
||||
|
||||
/* Configure MIPI CAL. */
|
||||
DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal01);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal02);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init11);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal03);
|
||||
DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal04);
|
||||
if (g_is_mariko) {
|
||||
/* On Mariko the above configurations are executed twice, for some reason. */
|
||||
i2c::driver::OpenSession(&i2c_session, I2cDevice_Max77620Pmic);
|
||||
|
||||
if (g_is_mariko) {
|
||||
WriteI2cRegister(i2c_session, 0x18, 0x3A);
|
||||
WriteI2cRegister(i2c_session, 0x1F, 0x71);
|
||||
}
|
||||
WriteI2cRegister(i2c_session, 0x23, 0xD0);
|
||||
}
|
||||
|
||||
/* Enable MIPI CAL, DSI, DISP1, HOST1X, UART_FST_MIPI_CAL, DSIA LP clocks. */
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_H_CLR, 0x1010000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_H_SET, 0x1010000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_L_CLR, 0x18000000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_L_SET, 0x18000000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_X_SET, 0x20000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_SOURCE_UART_FST_MIPI_CAL, 0xA);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_W_SET, 0x80000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_SOURCE_DSIA_LP, 0xA);
|
||||
|
||||
/* DPD idle. */
|
||||
WritePmcRegister(PmcBase + APBDEV_PMC_IO_DPD_REQ, 0x40000000);
|
||||
WritePmcRegister(PmcBase + APBDEV_PMC_IO_DPD2_REQ, 0x40000000);
|
||||
|
||||
/* Configure LCD pinmux tristate + passthrough. */
|
||||
reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_NFC_EN, PINMUX_TRISTATE);
|
||||
reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_NFC_INT, PINMUX_TRISTATE);
|
||||
reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, PINMUX_TRISTATE);
|
||||
reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_EN, PINMUX_TRISTATE);
|
||||
reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_RST, PINMUX_TRISTATE);
|
||||
|
||||
/* Configure LCD power, VDD. */
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT3_CNF_0, 0x3);
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT3_OE_0, 0x3);
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x1);
|
||||
svcSleepThread(10'000'000ul);
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x2);
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Configure LCD backlight. */
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x7);
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT6_OE_1, 0x7);
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x2);
|
||||
|
||||
/* Configure display interface and display. */
|
||||
reg::Write(g_mipi_cal_regs + 0x060, 0);
|
||||
if (g_is_mariko) {
|
||||
reg::Write(g_mipi_cal_regs + 0x058, 0);
|
||||
reg::Write(g_apb_misc_regs + 0xAC0, 0);
|
||||
}
|
||||
|
||||
/* Execute configs. */
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld01);
|
||||
DO_REGISTER_WRITES(g_disp1_regs, DisplayConfigDc01);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init01);
|
||||
/* NOTE: Nintendo bug here. */
|
||||
/* As of 8.0.0, Nintendo writes this list to CAR instead of DSI */
|
||||
/* This results in them zeroing CLK_SOURCE_UARTA... */
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init02);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init03);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init04);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init05);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init06);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init07);
|
||||
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Enable backlight reset. */
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x4);
|
||||
svcSleepThread(60'000'000ul);
|
||||
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_BTA_TIMING, 0x50204);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x337);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
WaitDsiTrigger();
|
||||
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x406);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
WaitDsiTrigger();
|
||||
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_TX_TRIG_HOST | DSI_HOST_CONTROL_IMM_BTA | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC);
|
||||
WaitDsiHostControl();
|
||||
svcSleepThread(5'000'000ul);
|
||||
|
||||
/* Parse LCD vendor. */
|
||||
{
|
||||
u32 host_response[3];
|
||||
for (size_t i = 0; i < sizeof(host_response) / sizeof(host_response[0]); i++) {
|
||||
host_response[i] = reg::Read(g_dsi_regs + sizeof(u32) * DSI_RD_DATA);
|
||||
}
|
||||
|
||||
if ((host_response[2] & 0xFF) == 0x10) {
|
||||
g_lcd_vendor = 0;
|
||||
} else {
|
||||
g_lcd_vendor = (host_response[2] >> 8) & 0xFF00;
|
||||
}
|
||||
g_lcd_vendor = (g_lcd_vendor & 0xFFFFFF00) | (host_response[2] & 0xFF);
|
||||
}
|
||||
|
||||
/* LCD vendor specific configuration. */
|
||||
switch (g_lcd_vendor) {
|
||||
case 0xF30: /* TODO: What's this? */
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(180'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x739);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x711148B1);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x143209);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
break;
|
||||
case 0xF20: /* TODO: What's this? */
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(180'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x739);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x751548B1);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x143209);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
break;
|
||||
case 0x10: /* Japan Display Inc screens. */
|
||||
DO_DSI_SLEEP_OR_REGISTER_WRITES(DisplayConfigJdiSpecificInit01);
|
||||
break;
|
||||
default:
|
||||
if ((g_lcd_vendor | 0x10) == 0x1030) {
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(120'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
}
|
||||
break;
|
||||
}
|
||||
svcSleepThread(20'000'000ul);
|
||||
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld02);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init08);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init09);
|
||||
|
||||
reg::Write(g_disp1_regs + sizeof(u32) * DC_DISP_DISP_CLOCK_CONTROL, SHIFT_CLK_DIVIDER(4));
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init10);
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Configure MIPI CAL. */
|
||||
DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal01);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal02);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init11);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal03);
|
||||
DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal04);
|
||||
}
|
||||
svcSleepThread(10'000'000ul);
|
||||
if (g_is_mariko) {
|
||||
/* On Mariko the above configurations are executed twice, for some reason. */
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal02);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init11);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal03);
|
||||
DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal04);
|
||||
}
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Write DISP1, FrameBuffer config. */
|
||||
DO_REGISTER_WRITES(g_disp1_regs, DisplayConfigDc02);
|
||||
DO_REGISTER_WRITES(g_disp1_regs, DisplayConfigFrameBuffer);
|
||||
svcSleepThread(35'000'000ul);
|
||||
g_is_display_intialized = true;
|
||||
}
|
||||
|
||||
void Boot::ShowDisplay(size_t x, size_t y, size_t width, size_t height, const u32 *img) {
|
||||
if (!g_is_display_intialized) {
|
||||
return;
|
||||
/* Write DISP1, FrameBuffer config. */
|
||||
DO_REGISTER_WRITES(g_disp1_regs, DisplayConfigDc02);
|
||||
DO_REGISTER_WRITES(g_disp1_regs, DisplayConfigFrameBuffer);
|
||||
svcSleepThread(35'000'000ul);
|
||||
g_is_display_intialized = true;
|
||||
}
|
||||
|
||||
/* Draw the image to the screen. */
|
||||
std::memset(g_frame_buffer, 0, FrameBufferSize);
|
||||
{
|
||||
for (size_t cur_y = 0; cur_y < height; cur_y++) {
|
||||
for (size_t cur_x = 0; cur_x < width; cur_x++) {
|
||||
g_frame_buffer[(FrameBufferHeight - (x + cur_x)) * FrameBufferWidth + y + cur_y] = img[cur_y * width + cur_x];
|
||||
void ShowDisplay(size_t x, size_t y, size_t width, size_t height, const u32 *img) {
|
||||
if (!g_is_display_intialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Draw the image to the screen. */
|
||||
std::memset(g_frame_buffer, 0, FrameBufferSize);
|
||||
{
|
||||
for (size_t cur_y = 0; cur_y < height; cur_y++) {
|
||||
for (size_t cur_x = 0; cur_x < width; cur_x++) {
|
||||
g_frame_buffer[(FrameBufferHeight - (x + cur_x)) * FrameBufferWidth + y + cur_y] = img[cur_y * width + cur_x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
armDCacheFlush(g_frame_buffer, FrameBufferSize);
|
||||
armDCacheFlush(g_frame_buffer, FrameBufferSize);
|
||||
|
||||
/* Enable backlight. */
|
||||
SetRegisterBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x1);
|
||||
}
|
||||
|
||||
void Boot::FinalizeDisplay() {
|
||||
if (!g_is_display_intialized) {
|
||||
return;
|
||||
/* Enable backlight. */
|
||||
reg::SetBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x1);
|
||||
}
|
||||
|
||||
/* Disable backlight. */
|
||||
ClearRegisterBits(g_gpio_regs + GPIO_PORT6_OUT_1, ~0x1);
|
||||
|
||||
WriteRegister(g_disp1_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 1);
|
||||
WriteRegister(g_disp1_regs + sizeof(u32) * DSI_WR_DATA, 0x2805);
|
||||
|
||||
/* Nintendo waits 5 frames before continuing. */
|
||||
{
|
||||
const uintptr_t host1x_vaddr = QueryVirtualAddress(0x500030a4, 4);
|
||||
const u32 start_val = ReadRegister(host1x_vaddr);
|
||||
while (ReadRegister(host1x_vaddr) < start_val + 5) {
|
||||
/* spinlock here. */
|
||||
void FinalizeDisplay() {
|
||||
if (!g_is_display_intialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Disable backlight. */
|
||||
reg::ClearBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x1);
|
||||
|
||||
reg::Write(g_disp1_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 1);
|
||||
reg::Write(g_disp1_regs + sizeof(u32) * DSI_WR_DATA, 0x2805);
|
||||
|
||||
/* Nintendo waits 5 frames before continuing. */
|
||||
{
|
||||
const uintptr_t host1x_vaddr = GetIoMapping(0x500030a4, 4);
|
||||
const u32 start_val = reg::Read(host1x_vaddr);
|
||||
while (reg::Read(host1x_vaddr) < start_val + 5) {
|
||||
/* spinlock here. */
|
||||
}
|
||||
}
|
||||
|
||||
reg::Write(g_disp1_regs + sizeof(u32) * DC_CMD_STATE_ACCESS, (READ_MUX | WRITE_MUX));
|
||||
reg::Write(g_disp1_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 0);
|
||||
|
||||
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld01);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Fini01);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Fini02);
|
||||
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Vendor specific shutdown. */
|
||||
switch (g_lcd_vendor) {
|
||||
case 0x10: /* Japan Display Inc screens. */
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigJdiSpecificFini01);
|
||||
break;
|
||||
case 0xF30: /* TODO: What's this? */
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigF30SpecificFini01);
|
||||
svcSleepThread(5'000'000ul);
|
||||
break;
|
||||
case 0x1020: /* TODO: What's this? */
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0xB39);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x751548B1);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x71143209);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x115631);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
break;
|
||||
case 0x1030: /* TODO: What's this? */
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0xB39);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x711148B1);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x71143209);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x114D31);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
svcSleepThread(5'000'000ul);
|
||||
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1005);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(50'000'000ul);
|
||||
|
||||
/* Disable backlight RST/Voltage. */
|
||||
reg::ClearBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x4);
|
||||
svcSleepThread(10'000'000ul);
|
||||
reg::ClearBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x2);
|
||||
svcSleepThread(10'000'000ul);
|
||||
reg::ClearBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x1);
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Cut clock to DSI. */
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_H_SET, 0x1010000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_H_CLR, 0x1010000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_L_SET, 0x18000000);
|
||||
reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, 0x18000000);
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_PAD_CONTROL_0, (DSI_PAD_CONTROL_VS1_PULLDN_CLK | DSI_PAD_CONTROL_VS1_PULLDN(0xF) | DSI_PAD_CONTROL_VS1_PDIO_CLK | DSI_PAD_CONTROL_VS1_PDIO(0xF)));
|
||||
reg::Write(g_dsi_regs + sizeof(u32) * DSI_POWER_CONTROL, 0);
|
||||
|
||||
/* Final LCD config for PWM */
|
||||
reg::ClearBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x1);
|
||||
reg::SetBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, PINMUX_TRISTATE);
|
||||
reg::ReadWrite(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, 1, 0x3);
|
||||
|
||||
/* Unmap framebuffer from DC virtual address space. */
|
||||
FinalizeFrameBuffer();
|
||||
g_is_display_intialized = false;
|
||||
}
|
||||
|
||||
WriteRegister(g_disp1_regs + sizeof(u32) * DC_CMD_STATE_ACCESS, (READ_MUX | WRITE_MUX));
|
||||
WriteRegister(g_disp1_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 0);
|
||||
|
||||
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld01);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Fini01);
|
||||
DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming);
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Fini02);
|
||||
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Vendor specific shutdown. */
|
||||
switch (g_lcd_vendor) {
|
||||
case 0x10: /* Japan Display Inc screens. */
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigJdiSpecificFini01);
|
||||
break;
|
||||
case 0xF30: /* TODO: What's this? */
|
||||
DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigF30SpecificFini01);
|
||||
svcSleepThread(5'000'000ul);
|
||||
break;
|
||||
case 0x1020: /* TODO: What's this? */
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0xB39);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x751548B1);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x71143209);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x115631);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
break;
|
||||
case 0x1030: /* TODO: What's this? */
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x439);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x9483FFB9);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0xB39);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x711148B1);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x71143209);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x114D31);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(5'000'000ul);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
svcSleepThread(5'000'000ul);
|
||||
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1005);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST);
|
||||
svcSleepThread(50'000'000ul);
|
||||
|
||||
/* Disable backlight RST/Voltage. */
|
||||
ClearRegisterBits(g_gpio_regs + GPIO_PORT6_OUT_1, ~0x4);
|
||||
svcSleepThread(10'000'000ul);
|
||||
ClearRegisterBits(g_gpio_regs + GPIO_PORT3_OUT_0, ~0x2);
|
||||
svcSleepThread(10'000'000ul);
|
||||
ClearRegisterBits(g_gpio_regs + GPIO_PORT3_OUT_0, ~0x1);
|
||||
svcSleepThread(10'000'000ul);
|
||||
|
||||
/* Cut clock to DSI. */
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_H_SET, 0x1010000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_H_CLR, 0x1010000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_RST_DEV_L_SET, 0x18000000);
|
||||
WriteRegister(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, 0x18000000);
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_PAD_CONTROL_0, (DSI_PAD_CONTROL_VS1_PULLDN_CLK | DSI_PAD_CONTROL_VS1_PULLDN(0xF) | DSI_PAD_CONTROL_VS1_PDIO_CLK | DSI_PAD_CONTROL_VS1_PDIO(0xF)));
|
||||
WriteRegister(g_dsi_regs + sizeof(u32) * DSI_POWER_CONTROL, 0);
|
||||
|
||||
/* Final LCD config for PWM */
|
||||
ClearRegisterBits(g_gpio_regs + GPIO_PORT6_CNF_1, ~0x1);
|
||||
SetRegisterBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, PINMUX_TRISTATE);
|
||||
ReadWriteRegisterBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, 1, 0x3);
|
||||
|
||||
/* Unmap framebuffer from DC virtual address space. */
|
||||
FinalizeFrameBuffer();
|
||||
g_is_display_intialized = false;
|
||||
}
|
||||
}
|
||||
|
28
stratosphere/boot/source/boot_display.hpp
Normal file
28
stratosphere/boot/source/boot_display.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
/* Splash Screen/Display utilities. */
|
||||
void InitializeDisplay();
|
||||
void ShowDisplay(size_t x, size_t y, size_t width, size_t height, const u32 *img);
|
||||
void FinalizeDisplay();
|
||||
|
||||
}
|
@ -14,17 +14,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
#include "boot_registers_clkrst.hpp"
|
||||
#include "boot_registers_di.hpp"
|
||||
#include "boot_registers_gpio.hpp"
|
||||
#include "boot_registers_pinmux.hpp"
|
||||
#include "boot_registers_pmc.hpp"
|
||||
|
||||
struct RegisterWrite {
|
||||
u32 offset;
|
||||
u32 value;
|
||||
@ -41,21 +30,21 @@ struct DsiSleepOrRegisterWrite {
|
||||
u32 value;
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigPlld01Erista[] = {
|
||||
constexpr RegisterWrite DisplayConfigPlld01Erista[] = {
|
||||
{CLK_RST_CONTROLLER_CLK_SOURCE_DISP1, 0x40000000},
|
||||
{CLK_RST_CONTROLLER_PLLD_BASE, 0x4830A001},
|
||||
{CLK_RST_CONTROLLER_PLLD_MISC1, 0x00000020},
|
||||
{CLK_RST_CONTROLLER_PLLD_MISC, 0x002D0AAA},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigPlld01Mariko[] = {
|
||||
constexpr RegisterWrite DisplayConfigPlld01Mariko[] = {
|
||||
{CLK_RST_CONTROLLER_CLK_SOURCE_DISP1, 0x40000000},
|
||||
{CLK_RST_CONTROLLER_PLLD_BASE, 0x4830A001},
|
||||
{CLK_RST_CONTROLLER_PLLD_MISC1, 0x00000000},
|
||||
{CLK_RST_CONTROLLER_PLLD_MISC, 0x002DFC00},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDc01[] = {
|
||||
constexpr RegisterWrite DisplayConfigDc01[] = {
|
||||
{sizeof(u32) * DC_CMD_STATE_ACCESS, 0},
|
||||
{sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_UPDATE},
|
||||
{sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ},
|
||||
@ -158,7 +147,7 @@ static constexpr RegisterWrite DisplayConfigDc01[] = {
|
||||
{sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ | WIN_B_ACT_REQ | WIN_C_ACT_REQ}
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init01[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init01[] = {
|
||||
{sizeof(u32) * DSI_WR_DATA, 0x0},
|
||||
{sizeof(u32) * DSI_INT_ENABLE, 0x0},
|
||||
{sizeof(u32) * DSI_INT_STATUS, 0x0},
|
||||
@ -169,15 +158,15 @@ static constexpr RegisterWrite DisplayConfigDsi01Init01[] = {
|
||||
{sizeof(u32) * DSI_INIT_SEQ_DATA_3, 0x0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init02Erista[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init02Erista[] = {
|
||||
{sizeof(u32) * DSI_INIT_SEQ_DATA_15, 0x0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init02Mariko[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init02Mariko[] = {
|
||||
{sizeof(u32) * DSI_INIT_SEQ_DATA_15_MARIKO, 0x0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init03[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init03[] = {
|
||||
{sizeof(u32) * DSI_DCS_CMDS, 0},
|
||||
{sizeof(u32) * DSI_PKT_SEQ_0_LO, 0},
|
||||
{sizeof(u32) * DSI_PKT_SEQ_1_LO, 0},
|
||||
@ -193,11 +182,11 @@ static constexpr RegisterWrite DisplayConfigDsi01Init03[] = {
|
||||
{sizeof(u32) * DSI_PKT_SEQ_5_HI, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init04Erista[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init04Erista[] = {
|
||||
/* No register writes. */
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init04Mariko[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init04Mariko[] = {
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_1, 0},
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_2, 0},
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_3, 0},
|
||||
@ -207,7 +196,7 @@ static constexpr RegisterWrite DisplayConfigDsi01Init04Mariko[] = {
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_7_MARIKO, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init05[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init05[] = {
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_CD, 0},
|
||||
{sizeof(u32) * DSI_SOL_DELAY, 0x18},
|
||||
{sizeof(u32) * DSI_MAX_THRESHOLD, 0x1E0},
|
||||
@ -220,7 +209,7 @@ static constexpr RegisterWrite DisplayConfigDsi01Init05[] = {
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_1, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init06[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init06[] = {
|
||||
{sizeof(u32) * DSI_PHY_TIMING_1, 0x40A0E05},
|
||||
{sizeof(u32) * DSI_PHY_TIMING_2, 0x30109},
|
||||
{sizeof(u32) * DSI_BTA_TIMING, 0x190A14},
|
||||
@ -236,7 +225,7 @@ static constexpr RegisterWrite DisplayConfigDsi01Init06[] = {
|
||||
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init07[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init07[] = {
|
||||
{sizeof(u32) * DSI_PHY_TIMING_1, 0x40A0E05},
|
||||
{sizeof(u32) * DSI_PHY_TIMING_2, 0x30118},
|
||||
{sizeof(u32) * DSI_BTA_TIMING, 0x190A14},
|
||||
@ -253,15 +242,15 @@ static constexpr RegisterWrite DisplayConfigDsi01Init07[] = {
|
||||
{sizeof(u32) * DSI_INIT_SEQ_CONTROL, 0}
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsiPhyTimingErista[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsiPhyTimingErista[] = {
|
||||
{sizeof(u32) * DSI_PHY_TIMING_0, 0x6070601},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsiPhyTimingMariko[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsiPhyTimingMariko[] = {
|
||||
{sizeof(u32) * DSI_PHY_TIMING_0, 0x6070603},
|
||||
};
|
||||
|
||||
static constexpr DsiSleepOrRegisterWrite DisplayConfigJdiSpecificInit01[] = {
|
||||
constexpr DsiSleepOrRegisterWrite DisplayConfigJdiSpecificInit01[] = {
|
||||
{DsiSleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x439},
|
||||
{DsiSleepOrRegisterWriteKind_Write, DSI_WR_DATA, 0x9483FFB9},
|
||||
{DsiSleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
|
||||
@ -312,23 +301,23 @@ static constexpr DsiSleepOrRegisterWrite DisplayConfigJdiSpecificInit01[] = {
|
||||
{DsiSleepOrRegisterWriteKind_Write, DSI_TRIGGER, DSI_TRIGGER_HOST},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigPlld02Erista[] = {
|
||||
constexpr RegisterWrite DisplayConfigPlld02Erista[] = {
|
||||
{CLK_RST_CONTROLLER_PLLD_BASE, 0x4810c001},
|
||||
{CLK_RST_CONTROLLER_PLLD_MISC1, 0x00000020},
|
||||
{CLK_RST_CONTROLLER_PLLD_MISC, 0x002D0AAA},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigPlld02Mariko[] = {
|
||||
constexpr RegisterWrite DisplayConfigPlld02Mariko[] = {
|
||||
{CLK_RST_CONTROLLER_PLLD_BASE, 0x4810c001},
|
||||
{CLK_RST_CONTROLLER_PLLD_MISC1, 0x00000000},
|
||||
{CLK_RST_CONTROLLER_PLLD_MISC, 0x002DFC00},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init08[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init08[] = {
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_1, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init09[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init09[] = {
|
||||
{sizeof(u32) * DSI_PHY_TIMING_1, 0x40A0E05},
|
||||
{sizeof(u32) * DSI_PHY_TIMING_2, 0x30172},
|
||||
{sizeof(u32) * DSI_BTA_TIMING, 0x190A14},
|
||||
@ -350,7 +339,7 @@ static constexpr RegisterWrite DisplayConfigDsi01Init09[] = {
|
||||
{sizeof(u32) * DSI_HOST_CONTROL, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init10[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init10[] = {
|
||||
{sizeof(u32) * DSI_TRIGGER, 0},
|
||||
{sizeof(u32) * DSI_CONTROL, 0},
|
||||
{sizeof(u32) * DSI_SOL_DELAY, 6},
|
||||
@ -363,14 +352,14 @@ static constexpr RegisterWrite DisplayConfigDsi01Init10[] = {
|
||||
{sizeof(u32) * DSI_HOST_CONTROL, DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC}
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init11Erista[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init11Erista[] = {
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_1, 0},
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_2, 0},
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_3, DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3)},
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_4, 0}
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Init11Mariko[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Init11Mariko[] = {
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_1, 0},
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_2, 0},
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_3, 0},
|
||||
@ -380,24 +369,24 @@ static constexpr RegisterWrite DisplayConfigDsi01Init11Mariko[] = {
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_7_MARIKO, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigMipiCal01[] = {
|
||||
constexpr RegisterWrite DisplayConfigMipiCal01[] = {
|
||||
{0x60, 0},
|
||||
{0x08, 0xF3F10000},
|
||||
{0x58, 1},
|
||||
{0x60, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigMipiCal02Erista[] = {
|
||||
constexpr RegisterWrite DisplayConfigMipiCal02Erista[] = {
|
||||
{0x60, 0x10010},
|
||||
{0x5C, 0x300},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigMipiCal02Mariko[] = {
|
||||
constexpr RegisterWrite DisplayConfigMipiCal02Mariko[] = {
|
||||
{0x60, 0x10010},
|
||||
{0x5C, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigMipiCal03Erista[] = {
|
||||
constexpr RegisterWrite DisplayConfigMipiCal03Erista[] = {
|
||||
{0x38, 0x200200},
|
||||
{0x3C, 0x200200},
|
||||
{0x64, 0x200002},
|
||||
@ -406,7 +395,7 @@ static constexpr RegisterWrite DisplayConfigMipiCal03Erista[] = {
|
||||
{0x18, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigMipiCal03Mariko[] = {
|
||||
constexpr RegisterWrite DisplayConfigMipiCal03Mariko[] = {
|
||||
{0x38, 0x200006},
|
||||
{0x3C, 0x200006},
|
||||
{0x64, 0x260000},
|
||||
@ -415,7 +404,7 @@ static constexpr RegisterWrite DisplayConfigMipiCal03Mariko[] = {
|
||||
{0x18, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigMipiCal04[] = {
|
||||
constexpr RegisterWrite DisplayConfigMipiCal04[] = {
|
||||
{0x1C, 0},
|
||||
{0x20, 0},
|
||||
{0x24, 0},
|
||||
@ -428,7 +417,7 @@ static constexpr RegisterWrite DisplayConfigMipiCal04[] = {
|
||||
{0x00, 0x2A000001},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDc02[] = {
|
||||
constexpr RegisterWrite DisplayConfigDc02[] = {
|
||||
{sizeof(u32) * DC_CMD_STATE_ACCESS, 0},
|
||||
{sizeof(u32) * DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_A_SELECT},
|
||||
{sizeof(u32) * DC_WIN_WIN_OPTIONS, 0},
|
||||
@ -552,9 +541,9 @@ static constexpr RegisterWrite DisplayConfigDc02[] = {
|
||||
{sizeof(u32) * DC_CMD_DISPLAY_COMMAND_OPTION0, 0}
|
||||
};
|
||||
|
||||
static constexpr u32 DisplayConfigFrameBufferAddress = 0xC0000000;
|
||||
constexpr u32 DisplayConfigFrameBufferAddress = 0xC0000000;
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigFrameBuffer[] = {
|
||||
constexpr RegisterWrite DisplayConfigFrameBuffer[] = {
|
||||
{sizeof(u32) * DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_C_SELECT}, //Enable window C.
|
||||
{sizeof(u32) * DC_WIN_WIN_OPTIONS, 0},
|
||||
{sizeof(u32) * DC_CMD_DISPLAY_WINDOW_HEADER, WINDOW_B_SELECT}, //Enable window B.
|
||||
@ -589,12 +578,12 @@ static constexpr RegisterWrite DisplayConfigFrameBuffer[] = {
|
||||
{sizeof(u32) * DC_CMD_STATE_CONTROL, GENERAL_ACT_REQ | WIN_A_ACT_REQ} //General activation request; window A activation request.
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Fini01[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Fini01[] = {
|
||||
{sizeof(u32) * DSI_POWER_CONTROL, 0},
|
||||
{sizeof(u32) * DSI_PAD_CONTROL_1, 0},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigDsi01Fini02[] = {
|
||||
constexpr RegisterWrite DisplayConfigDsi01Fini02[] = {
|
||||
{sizeof(u32) * DSI_PHY_TIMING_1, 0x40A0E05},
|
||||
{sizeof(u32) * DSI_PHY_TIMING_2, 0x30109},
|
||||
{sizeof(u32) * DSI_BTA_TIMING, 0x190A14},
|
||||
@ -610,7 +599,7 @@ static constexpr RegisterWrite DisplayConfigDsi01Fini02[] = {
|
||||
{sizeof(u32) * DSI_INIT_SEQ_CONTROL, 0}
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigJdiSpecificFini01[] = {
|
||||
constexpr RegisterWrite DisplayConfigJdiSpecificFini01[] = {
|
||||
{sizeof(u32) * DSI_WR_DATA, 0x439},
|
||||
{sizeof(u32) * DSI_WR_DATA, 0x9483FFB9},
|
||||
{sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST},
|
||||
@ -635,7 +624,7 @@ static constexpr RegisterWrite DisplayConfigJdiSpecificFini01[] = {
|
||||
{sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST},
|
||||
};
|
||||
|
||||
static constexpr RegisterWrite DisplayConfigF30SpecificFini01[] = {
|
||||
constexpr RegisterWrite DisplayConfigF30SpecificFini01[] = {
|
||||
{sizeof(u32) * DSI_WR_DATA, 0x439},
|
||||
{sizeof(u32) * DSI_WR_DATA, 0x9483FFB9},
|
||||
{sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST},
|
@ -14,14 +14,27 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include <stratosphere/spl.hpp>
|
||||
|
||||
static constexpr u32 GpioPadName_FanEnable = 0x4B;
|
||||
#include "boot_fan_enable.hpp"
|
||||
|
||||
#include "gpio/gpio_utils.hpp"
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr u32 GpioPadName_FanEnable = 0x4B;
|
||||
|
||||
void Boot::SetFanEnabled() {
|
||||
if (Boot::GetHardwareType() == HardwareType_Copper) {
|
||||
Boot::GpioConfigure(GpioPadName_FanEnable);
|
||||
Boot::GpioSetDirection(GpioPadName_FanEnable, GpioDirection_Output);
|
||||
Boot::GpioSetValue(GpioPadName_FanEnable, GpioValue_High);
|
||||
}
|
||||
|
||||
void SetFanEnabled() {
|
||||
if (spl::GetHardwareType() == spl::HardwareType::Copper) {
|
||||
gpio::Configure(GpioPadName_FanEnable);
|
||||
gpio::SetDirection(GpioPadName_FanEnable, GpioDirection_Output);
|
||||
gpio::SetValue(GpioPadName_FanEnable, GpioValue_High);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
25
stratosphere/boot/source/boot_fan_enable.hpp
Normal file
25
stratosphere/boot/source/boot_fan_enable.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
void SetFanEnabled();
|
||||
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
#include "i2c_driver/i2c_types.hpp"
|
||||
|
||||
class Boot {
|
||||
public:
|
||||
static constexpr u32 GpioPhysicalBase = 0x6000D000;
|
||||
static constexpr u32 ApbMiscPhysicalBase = 0x70000000;
|
||||
public:
|
||||
/* Functions for actually booting. */
|
||||
static void ChangeGpioVoltageTo1_8v();
|
||||
static void SetInitialGpioConfiguration();
|
||||
static void CheckClock();
|
||||
static void DetectBootReason();
|
||||
static void ShowSplashScreen();
|
||||
static void CheckBatteryCharge();
|
||||
static void SetInitialClockConfiguration();
|
||||
static void ConfigurePinmux();
|
||||
static void SetInitialWakePinConfiguration();
|
||||
static void SetFanEnabled();
|
||||
static void CheckAndRepairBootImages();
|
||||
|
||||
/* Power utilities. */
|
||||
static void RebootSystem();
|
||||
static void ShutdownSystem();
|
||||
|
||||
/* Register Utilities. */
|
||||
static u32 ReadPmcRegister(u32 phys_addr);
|
||||
static void WritePmcRegister(u32 phys_addr, u32 value, u32 mask = UINT32_MAX);
|
||||
|
||||
/* GPIO Utilities. */
|
||||
static u32 GpioConfigure(u32 gpio_pad_name);
|
||||
static u32 GpioSetDirection(u32 gpio_pad_name, GpioDirection dir);
|
||||
static u32 GpioSetValue(u32 gpio_pad_name, GpioValue val);
|
||||
|
||||
/* Pinmux Utilities. */
|
||||
static u32 PinmuxUpdatePark(u32 pinmux_name);
|
||||
static u32 PinmuxUpdatePad(u32 pinmux_name, u32 config_val, u32 config_mask);
|
||||
static u32 PinmuxUpdateDrivePad(u32 pinmux_drivepad_name, u32 config_val, u32 config_mask);
|
||||
static u32 PinmuxDummyReadDrivePad(u32 pinmux_drivepad_name);
|
||||
static void ConfigurePinmuxInitialPads();
|
||||
static void ConfigurePinmuxInitialDrivePads();
|
||||
|
||||
/* SPL Utilities. */
|
||||
static HardwareType GetHardwareType();
|
||||
static u32 GetBootReason();
|
||||
static bool IsRecoveryBoot();
|
||||
static bool IsMariko();
|
||||
|
||||
/* I2C Utilities. */
|
||||
static Result ReadI2cRegister(I2cSessionImpl &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size);
|
||||
static Result WriteI2cRegister(I2cSessionImpl &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size);
|
||||
static Result WriteI2cRegister(I2cSessionImpl &session, const u8 address, const u8 value);
|
||||
|
||||
/* Splash Screen/Display utilities. */
|
||||
static void InitializeDisplay();
|
||||
static void ShowDisplay(size_t x, size_t y, size_t width, size_t height, const u32 *img);
|
||||
static void FinalizeDisplay();
|
||||
|
||||
/* Battery Display utilities. */
|
||||
static void ShowLowBatteryIcon();
|
||||
static void StartShowChargingIcon(size_t battery_percentage, bool wait);
|
||||
static void EndShowChargingIcon();
|
||||
|
||||
/* Calibration utilities. */
|
||||
static u16 GetCrc16(const void *data, size_t size);
|
||||
static u32 GetBatteryVersion();
|
||||
static u32 GetBatteryVendor();
|
||||
|
||||
/* Wake pin utiliies. */
|
||||
static void SetWakeEventLevel(u32 index, u32 level);
|
||||
static void SetWakeEventEnabled(u32 index, bool enabled);
|
||||
};
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* 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_functions.hpp"
|
||||
#include "boot_gpio_initial_configuration_icosa.hpp"
|
||||
#include "boot_gpio_initial_configuration_copper.hpp"
|
||||
#include "boot_gpio_initial_configuration_hoag.hpp"
|
||||
#include "boot_gpio_initial_configuration_iowa.hpp"
|
||||
|
||||
void Boot::SetInitialGpioConfiguration() {
|
||||
const GpioInitialConfig *configs = nullptr;
|
||||
size_t num_configs = 0;
|
||||
const HardwareType hw_type = Boot::GetHardwareType();
|
||||
const FirmwareVersion fw_ver = GetRuntimeFirmwareVersion();
|
||||
|
||||
/* Choose GPIO map. */
|
||||
if (fw_ver >= FirmwareVersion_200) {
|
||||
switch (hw_type) {
|
||||
case HardwareType_Icosa:
|
||||
{
|
||||
if (fw_ver >= FirmwareVersion_400) {
|
||||
configs = GpioInitialConfigsIcosa4x;
|
||||
num_configs = GpioNumInitialConfigsIcosa4x;
|
||||
} else {
|
||||
configs = GpioInitialConfigsIcosa;
|
||||
num_configs = GpioNumInitialConfigsIcosa;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HardwareType_Copper:
|
||||
configs = GpioInitialConfigsCopper;
|
||||
num_configs = GpioNumInitialConfigsCopper;
|
||||
break;
|
||||
case HardwareType_Hoag:
|
||||
configs = GpioInitialConfigsHoag;
|
||||
num_configs = GpioNumInitialConfigsHoag;
|
||||
break;
|
||||
case HardwareType_Iowa:
|
||||
configs = GpioInitialConfigsIowa;
|
||||
num_configs = GpioNumInitialConfigsIowa;
|
||||
break;
|
||||
default:
|
||||
/* Unknown hardware type, we can't proceed. */
|
||||
std::abort();
|
||||
}
|
||||
} else {
|
||||
/* Until 2.0.0, the GPIO map for Icosa was used for all hardware types. */
|
||||
configs = GpioInitialConfigsIcosa;
|
||||
num_configs = GpioNumInitialConfigsIcosa;
|
||||
}
|
||||
|
||||
/* Ensure we found an appropriate config. */
|
||||
if (configs == nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_configs; i++) {
|
||||
/* Configure the GPIO. */
|
||||
Boot::GpioConfigure(configs[i].pad_name);
|
||||
|
||||
/* Set the GPIO's direction. */
|
||||
Boot::GpioSetDirection(configs[i].pad_name, configs[i].direction);
|
||||
|
||||
if (configs[i].direction == GpioDirection_Output) {
|
||||
/* Set the GPIO's value. */
|
||||
Boot::GpioSetValue(configs[i].pad_name, configs[i].value);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
/*
|
||||
* 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_functions.hpp"
|
||||
#include "boot_gpio_map.hpp"
|
||||
|
||||
static bool g_initialized_gpio_vaddr = false;
|
||||
static uintptr_t g_gpio_vaddr = 0;
|
||||
|
||||
static inline u32 GetGpioPadDescriptor(u32 gpio_pad_name) {
|
||||
if (gpio_pad_name >= GpioPadNameMax) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return GpioMap[gpio_pad_name];
|
||||
}
|
||||
|
||||
static uintptr_t GetGpioBaseAddress() {
|
||||
if (!g_initialized_gpio_vaddr) {
|
||||
u64 vaddr;
|
||||
if (R_FAILED(svcQueryIoMapping(&vaddr, Boot::GpioPhysicalBase, 0x1000))) {
|
||||
std::abort();
|
||||
}
|
||||
g_gpio_vaddr = vaddr;
|
||||
g_initialized_gpio_vaddr = true;
|
||||
}
|
||||
return g_gpio_vaddr;
|
||||
}
|
||||
|
||||
u32 Boot::GpioConfigure(u32 gpio_pad_name) {
|
||||
uintptr_t gpio_base_vaddr = GetGpioBaseAddress();
|
||||
|
||||
/* Fetch this GPIO's pad descriptor */
|
||||
const u32 gpio_pad_desc = GetGpioPadDescriptor(gpio_pad_name);
|
||||
|
||||
/* Discard invalid GPIOs */
|
||||
if (gpio_pad_desc == GpioInvalid) {
|
||||
return GpioInvalid;
|
||||
}
|
||||
|
||||
/* Convert the GPIO pad descriptor into its register offset */
|
||||
u32 gpio_reg_offset = (((gpio_pad_desc << 0x03) & 0xFFFFFF00) | ((gpio_pad_desc >> 0x01) & 0x0C));
|
||||
|
||||
/* Extract the bit and lock values from the GPIO pad descriptor */
|
||||
u32 gpio_cnf_val = ((0x01 << ((gpio_pad_desc & 0x07) | 0x08)) | (0x01 << (gpio_pad_desc & 0x07)));
|
||||
|
||||
/* Write to the appropriate GPIO_CNF_x register (upper offset) */
|
||||
*(reinterpret_cast<volatile u32 *>(gpio_base_vaddr + gpio_reg_offset + 0x80)) = gpio_cnf_val;
|
||||
|
||||
/* Do a dummy read from GPIO_CNF_x register (lower offset) */
|
||||
gpio_cnf_val = *(reinterpret_cast<volatile u32 *>(gpio_base_vaddr + gpio_reg_offset));
|
||||
|
||||
return gpio_cnf_val;
|
||||
}
|
||||
|
||||
u32 Boot::GpioSetDirection(u32 gpio_pad_name, GpioDirection dir) {
|
||||
uintptr_t gpio_base_vaddr = GetGpioBaseAddress();
|
||||
|
||||
/* Fetch this GPIO's pad descriptor */
|
||||
const u32 gpio_pad_desc = GetGpioPadDescriptor(gpio_pad_name);
|
||||
|
||||
/* Discard invalid GPIOs */
|
||||
if (gpio_pad_desc == GpioInvalid) {
|
||||
return GpioInvalid;
|
||||
}
|
||||
|
||||
/* Convert the GPIO pad descriptor into its register offset */
|
||||
u32 gpio_reg_offset = (((gpio_pad_desc << 0x03) & 0xFFFFFF00) | ((gpio_pad_desc >> 0x01) & 0x0C));
|
||||
|
||||
/* Set the direction bit and lock values */
|
||||
u32 gpio_oe_val = ((0x01 << ((gpio_pad_desc & 0x07) | 0x08)) | (static_cast<u32>(dir) << (gpio_pad_desc & 0x07)));
|
||||
|
||||
/* Write to the appropriate GPIO_OE_x register (upper offset) */
|
||||
*(reinterpret_cast<volatile u32 *>(gpio_base_vaddr + gpio_reg_offset + 0x90)) = gpio_oe_val;
|
||||
|
||||
/* Do a dummy read from GPIO_OE_x register (lower offset) */
|
||||
gpio_oe_val = *(reinterpret_cast<volatile u32 *>(gpio_base_vaddr + gpio_reg_offset + 0x10));
|
||||
|
||||
return gpio_oe_val;
|
||||
}
|
||||
|
||||
u32 Boot::GpioSetValue(u32 gpio_pad_name, GpioValue val) {
|
||||
uintptr_t gpio_base_vaddr = GetGpioBaseAddress();
|
||||
|
||||
/* Fetch this GPIO's pad descriptor */
|
||||
const u32 gpio_pad_desc = GetGpioPadDescriptor(gpio_pad_name);
|
||||
|
||||
/* Discard invalid GPIOs */
|
||||
if (gpio_pad_desc == GpioInvalid) {
|
||||
return GpioInvalid;
|
||||
}
|
||||
|
||||
/* Convert the GPIO pad descriptor into its register offset */
|
||||
u32 gpio_reg_offset = (((gpio_pad_desc << 0x03) & 0xFFFFFF00) | ((gpio_pad_desc >> 0x01) & 0x0C));
|
||||
|
||||
/* Set the output bit and lock values */
|
||||
u32 gpio_out_val = ((0x01 << ((gpio_pad_desc & 0x07) | 0x08)) | (static_cast<u32>(val) << (gpio_pad_desc & 0x07)));
|
||||
|
||||
/* Write to the appropriate GPIO_OUT_x register (upper offset) */
|
||||
*(reinterpret_cast<volatile u32 *>(gpio_base_vaddr + gpio_reg_offset + 0xA0)) = gpio_out_val;
|
||||
|
||||
/* Do a dummy read from GPIO_OUT_x register (lower offset) */
|
||||
gpio_out_val = *(reinterpret_cast<volatile u32 *>(gpio_base_vaddr + gpio_reg_offset + 0x20));
|
||||
|
||||
return gpio_out_val;
|
||||
}
|
@ -14,55 +14,62 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "i2c_driver/i2c_api.hpp"
|
||||
#include "boot_i2c_utils.hpp"
|
||||
|
||||
template<typename F>
|
||||
static Result RetryUntilSuccess(F f) {
|
||||
constexpr u64 timeout = 10'000'000'000ul;
|
||||
constexpr u64 retry_interval = 20'000'000ul;
|
||||
namespace sts::boot {
|
||||
|
||||
u64 cur_time = 0;
|
||||
while (true) {
|
||||
R_TRY_CLEANUP(f(), {
|
||||
cur_time += retry_interval;
|
||||
if (cur_time < timeout) {
|
||||
svcSleepThread(retry_interval);
|
||||
continue;
|
||||
namespace {
|
||||
|
||||
template<typename F>
|
||||
constexpr Result RetryUntilSuccess(F f) {
|
||||
constexpr u64 timeout = 10'000'000'000ul;
|
||||
constexpr u64 retry_interval = 20'000'000ul;
|
||||
|
||||
u64 cur_time = 0;
|
||||
while (true) {
|
||||
R_TRY_CLEANUP(f(), {
|
||||
cur_time += retry_interval;
|
||||
if (cur_time < timeout) {
|
||||
svcSleepThread(retry_interval);
|
||||
continue;
|
||||
}
|
||||
});
|
||||
return ResultSuccess;
|
||||
}
|
||||
});
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result Boot::ReadI2cRegister(I2cSessionImpl &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size) {
|
||||
if (dst == nullptr || dst_size == 0 || cmd == nullptr || cmd_size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u8 cmd_list[I2cCommandListFormatter::MaxCommandListSize];
|
||||
Result ReadI2cRegister(i2c::driver::Session &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size) {
|
||||
if (dst == nullptr || dst_size == 0 || cmd == nullptr || cmd_size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
I2cCommandListFormatter formatter(cmd_list, sizeof(cmd_list));
|
||||
R_ASSERT(formatter.EnqueueSendCommand(I2cTransactionOption_Start, cmd, cmd_size));
|
||||
R_ASSERT(formatter.EnqueueReceiveCommand(static_cast<I2cTransactionOption>(I2cTransactionOption_Start | I2cTransactionOption_Stop), dst_size));
|
||||
u8 cmd_list[i2c::CommandListFormatter::MaxCommandListSize];
|
||||
|
||||
return RetryUntilSuccess([&]() { return I2cDriver::ExecuteCommandList(session, dst, dst_size, cmd_list, formatter.GetCurrentSize()); });
|
||||
}
|
||||
i2c::CommandListFormatter formatter(cmd_list, sizeof(cmd_list));
|
||||
R_ASSERT(formatter.EnqueueSendCommand(I2cTransactionOption_Start, cmd, cmd_size));
|
||||
R_ASSERT(formatter.EnqueueReceiveCommand(static_cast<I2cTransactionOption>(I2cTransactionOption_Start | I2cTransactionOption_Stop), dst_size));
|
||||
|
||||
Result Boot::WriteI2cRegister(I2cSessionImpl &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size) {
|
||||
if (src == nullptr || src_size == 0 || cmd == nullptr || cmd_size == 0) {
|
||||
std::abort();
|
||||
return RetryUntilSuccess([&]() { return i2c::driver::ExecuteCommandList(session, dst, dst_size, cmd_list, formatter.GetCurrentSize()); });
|
||||
}
|
||||
|
||||
u8 cmd_list[0x20];
|
||||
Result WriteI2cRegister(i2c::driver::Session &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size) {
|
||||
if (src == nullptr || src_size == 0 || cmd == nullptr || cmd_size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* N doesn't use a CommandListFormatter here... */
|
||||
std::memcpy(&cmd_list[0], cmd, cmd_size);
|
||||
std::memcpy(&cmd_list[cmd_size], src, src_size);
|
||||
u8 cmd_list[0x20];
|
||||
|
||||
/* N doesn't use a CommandListFormatter here... */
|
||||
std::memcpy(&cmd_list[0], cmd, cmd_size);
|
||||
std::memcpy(&cmd_list[cmd_size], src, src_size);
|
||||
|
||||
return RetryUntilSuccess([&]() { return i2c::driver::Send(session, cmd_list, src_size + cmd_size, static_cast<I2cTransactionOption>(I2cTransactionOption_Start | I2cTransactionOption_Stop)); });
|
||||
}
|
||||
|
||||
Result WriteI2cRegister(i2c::driver::Session &session, const u8 address, const u8 value) {
|
||||
return WriteI2cRegister(session, &value, sizeof(value), &address, sizeof(address));
|
||||
}
|
||||
|
||||
return RetryUntilSuccess([&]() { return I2cDriver::Send(session, cmd_list, src_size + cmd_size, static_cast<I2cTransactionOption>(I2cTransactionOption_Start | I2cTransactionOption_Stop)); });
|
||||
}
|
||||
|
||||
Result Boot::WriteI2cRegister(I2cSessionImpl &session, const u8 address, const u8 value) {
|
||||
return Boot::WriteI2cRegister(session, &value, sizeof(value), &address, sizeof(address));
|
||||
}
|
||||
|
@ -18,25 +18,13 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
/* TODO: Better way to do this? */
|
||||
#include "../boot_types.hpp"
|
||||
#include "i2c/driver/i2c_api.hpp"
|
||||
|
||||
enum BootImageUpdateType {
|
||||
BootImageUpdateType_Erista,
|
||||
BootImageUpdateType_Mariko,
|
||||
};
|
||||
namespace sts::boot {
|
||||
|
||||
enum BootModeType {
|
||||
BootModeType_Normal,
|
||||
BootModeType_Safe,
|
||||
};
|
||||
/* I2C Utilities. */
|
||||
Result ReadI2cRegister(i2c::driver::Session &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size);
|
||||
Result WriteI2cRegister(i2c::driver::Session &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size);
|
||||
Result WriteI2cRegister(i2c::driver::Session &session, const u8 address, const u8 value);
|
||||
|
||||
static constexpr size_t BctSize = 0x4000;
|
||||
static constexpr size_t EksSize = 0x4000;
|
||||
static constexpr size_t EksEntrySize = 0x200;
|
||||
static constexpr size_t EksBlobSize = 0xB0;
|
||||
|
||||
struct VerificationState {
|
||||
bool needs_verify_normal;
|
||||
bool needs_verify_safe;
|
||||
};
|
||||
}
|
@ -22,9 +22,24 @@
|
||||
#include <switch.h>
|
||||
#include <atmosphere.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/spl.hpp>
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_reboot_manager.hpp"
|
||||
#include "boot_boot_reason.hpp"
|
||||
#include "boot_change_voltage.hpp"
|
||||
#include "boot_check_battery.hpp"
|
||||
#include "boot_check_clock.hpp"
|
||||
#include "boot_clock_initial_configuration.hpp"
|
||||
#include "boot_fan_enable.hpp"
|
||||
#include "boot_repair_boot_images.hpp"
|
||||
#include "boot_splash_screen.hpp"
|
||||
#include "boot_wake_pins.hpp"
|
||||
|
||||
#include "gpio/gpio_initial_configuration.hpp"
|
||||
#include "pinmux/pinmux_initial_configuration.hpp"
|
||||
|
||||
#include "boot_power_utils.hpp"
|
||||
|
||||
using namespace sts;
|
||||
|
||||
extern "C" {
|
||||
extern u32 __start__;
|
||||
@ -53,7 +68,7 @@ void __libnx_exception_handler(ThreadExceptionDump *ctx) {
|
||||
|
||||
void __libstratosphere_exception_handler(AtmosphereFatalErrorContext *ctx) {
|
||||
/* We're boot sysmodule, so manually reboot to fatal error. */
|
||||
BootRebootManager::RebootForFatalError(ctx);
|
||||
boot::RebootForFatalError(ctx);
|
||||
}
|
||||
|
||||
void __libnx_initheap(void) {
|
||||
@ -94,47 +109,45 @@ int main(int argc, char **argv)
|
||||
consoleDebugInit(debugDevice_SVC);
|
||||
|
||||
/* Change voltage from 3.3v to 1.8v for select devices. */
|
||||
Boot::ChangeGpioVoltageTo1_8v();
|
||||
boot::ChangeGpioVoltageTo1_8v();
|
||||
|
||||
/* Setup GPIO. */
|
||||
Boot::SetInitialGpioConfiguration();
|
||||
gpio::SetInitialConfiguration();
|
||||
|
||||
/* Check USB PLL/UTMIP clock. */
|
||||
Boot::CheckClock();
|
||||
boot::CheckClock();
|
||||
|
||||
/* Talk to PMIC/RTC, set boot reason with SPL. */
|
||||
Boot::DetectBootReason();
|
||||
boot::DetectBootReason();
|
||||
|
||||
const HardwareType hw_type = Boot::GetHardwareType();
|
||||
if (hw_type != HardwareType_Copper) {
|
||||
const auto hw_type = spl::GetHardwareType();
|
||||
if (hw_type != spl::HardwareType::Copper) {
|
||||
/* Display splash screen for two seconds. */
|
||||
Boot::ShowSplashScreen();
|
||||
boot::ShowSplashScreen();
|
||||
|
||||
/* Check that the battery has enough to boot. */
|
||||
Boot::CheckBatteryCharge();
|
||||
boot::CheckBatteryCharge();
|
||||
}
|
||||
|
||||
/* Configure pinmux + drive pads. */
|
||||
Boot::ConfigurePinmux();
|
||||
pinmux::SetInitialConfiguration();
|
||||
|
||||
/* Configure the PMC wake pin settings. */
|
||||
Boot::SetInitialWakePinConfiguration();
|
||||
boot::SetInitialWakePinConfiguration();
|
||||
|
||||
/* Configure output clock. */
|
||||
if (hw_type != HardwareType_Copper) {
|
||||
Boot::SetInitialClockConfiguration();
|
||||
if (hw_type != spl::HardwareType::Copper) {
|
||||
boot::SetInitialClockConfiguration();
|
||||
}
|
||||
|
||||
/* Set Fan enable config (Copper only). */
|
||||
Boot::SetFanEnabled();
|
||||
boot::SetFanEnabled();
|
||||
|
||||
/* Repair boot partitions in NAND if needed. */
|
||||
Boot::CheckAndRepairBootImages();
|
||||
boot::CheckAndRepairBootImages();
|
||||
|
||||
/* Tell PM to start boot2. */
|
||||
if (R_FAILED(pmshellNotifyBootFinished())) {
|
||||
std::abort();
|
||||
}
|
||||
R_ASSERT(pmshellNotifyBootFinished());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
77
stratosphere/boot/source/boot_pcv.cpp
Normal file
77
stratosphere/boot/source/boot_pcv.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c/i2c_types.hpp"
|
||||
#include "i2c/driver/impl/i2c_pcv.hpp"
|
||||
#include "i2c/driver/impl/i2c_registers.hpp"
|
||||
|
||||
using namespace sts::i2c::driver::impl;
|
||||
|
||||
namespace sts::pcv {
|
||||
|
||||
void Initialize() {
|
||||
/* Don't do anything. */
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
/* Don't do anything. */
|
||||
}
|
||||
|
||||
Result SetClockRate(PcvModule module, u32 hz) {
|
||||
/* Get clock/reset registers. */
|
||||
ClkRstRegisters regs;
|
||||
regs.SetBus(ConvertFromPcvModule(module));
|
||||
/* Set clock enabled/source. */
|
||||
reg::SetBits(regs.clk_en_reg, regs.mask);
|
||||
reg::ReadWrite(regs.clk_src_reg, 0x4, 0xFF);
|
||||
svcSleepThread(1000ul);
|
||||
reg::ReadWrite(regs.clk_src_reg, 0, 0xE0000000);
|
||||
svcSleepThread(2000ul);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetClockEnabled(PcvModule module, bool enabled) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetVoltageEnabled(u32 domain, bool enabled) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetVoltageValue(u32 domain, u32 voltage) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetReset(PcvModule module, bool reset) {
|
||||
/* Get clock/reset registers. */
|
||||
ClkRstRegisters regs;
|
||||
regs.SetBus(ConvertFromPcvModule(module));
|
||||
|
||||
/* Set/clear reset. */
|
||||
if (reset) {
|
||||
reg::SetBits(regs.rst_reg, regs.mask);
|
||||
} else {
|
||||
reg::ClearBits(regs.rst_reg, regs.mask);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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_functions.hpp"
|
||||
#include "boot_pinmux_map.hpp"
|
||||
#include "boot_pinmux_initial_configuration_icosa.hpp"
|
||||
#include "boot_pinmux_initial_configuration_copper.hpp"
|
||||
#include "boot_pinmux_initial_configuration_hoag.hpp"
|
||||
#include "boot_pinmux_initial_configuration_iowa.hpp"
|
||||
#include "boot_pinmux_initial_drive_pad_configuration.hpp"
|
||||
|
||||
void Boot::ConfigurePinmux() {
|
||||
/* Update parks. */
|
||||
for (size_t i = 0; i < PinmuxPadNameMax; i++) {
|
||||
Boot::PinmuxUpdatePark(static_cast<u32>(i));
|
||||
}
|
||||
|
||||
/* Dummy read all drive pads. */
|
||||
for (size_t i = 0; i < PinmuxDrivePadNameMax; i++) {
|
||||
Boot::PinmuxDummyReadDrivePad(static_cast<u32>(i));
|
||||
}
|
||||
|
||||
/* Set initial pad configs. */
|
||||
Boot::ConfigurePinmuxInitialPads();
|
||||
|
||||
/* Set initial drive pad configs. */
|
||||
Boot::ConfigurePinmuxInitialDrivePads();
|
||||
}
|
||||
|
||||
void Boot::ConfigurePinmuxInitialPads() {
|
||||
const PinmuxInitialConfig *configs = nullptr;
|
||||
size_t num_configs = 0;
|
||||
const HardwareType hw_type = Boot::GetHardwareType();
|
||||
|
||||
switch (hw_type) {
|
||||
case HardwareType_Icosa:
|
||||
configs = PinmuxInitialConfigsIcosa;
|
||||
num_configs = PinmuxNumInitialConfigsIcosa;
|
||||
break;
|
||||
case HardwareType_Copper:
|
||||
configs = PinmuxInitialConfigsCopper;
|
||||
num_configs = PinmuxNumInitialConfigsCopper;
|
||||
break;
|
||||
case HardwareType_Hoag:
|
||||
configs = PinmuxInitialConfigsHoag;
|
||||
num_configs = PinmuxNumInitialConfigsHoag;
|
||||
break;
|
||||
case HardwareType_Iowa:
|
||||
configs = PinmuxInitialConfigsIowa;
|
||||
num_configs = PinmuxNumInitialConfigsIowa;
|
||||
break;
|
||||
default:
|
||||
/* Unknown hardware type, we can't proceed. */
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Ensure we found an appropriate config. */
|
||||
if (configs == nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_configs - 1; i++) {
|
||||
Boot::PinmuxUpdatePad(configs[i].name, configs[i].val, configs[i].mask);
|
||||
}
|
||||
|
||||
/* Extra configs for iowa only. */
|
||||
if (hw_type == HardwareType_Iowa) {
|
||||
static constexpr u32 ExtraIowaPinmuxPadNames[] = {
|
||||
0xAA, 0xAC, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(ExtraIowaPinmuxPadNames) / sizeof(ExtraIowaPinmuxPadNames[0]); i++) {
|
||||
Boot::PinmuxUpdatePad(ExtraIowaPinmuxPadNames[i], 0x2000, 0x2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Boot::ConfigurePinmuxInitialDrivePads() {
|
||||
const PinmuxInitialConfig *configs = PinmuxInitialDrivePadConfigs;
|
||||
for (size_t i = 0; i < PinmuxNumInitialDrivePadConfigs; i++) {
|
||||
Boot::PinmuxUpdateDrivePad(configs[i].name, configs[i].val, configs[i].mask);
|
||||
}
|
||||
}
|
@ -1,505 +0,0 @@
|
||||
/*
|
||||
* 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_functions.hpp"
|
||||
#include "boot_pinmux_map.hpp"
|
||||
|
||||
static bool g_initialized_pinmux_vaddr = false;
|
||||
static uintptr_t g_pinmux_vaddr = 0;
|
||||
|
||||
static inline const PinmuxDefinition *GetPinmuxDefinition(u32 pinmux_name) {
|
||||
if (pinmux_name >= PinmuxPadNameMax) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return &PinmuxMap[pinmux_name];
|
||||
}
|
||||
|
||||
static inline const PinmuxDrivePadDefinition *GetPinmuxDrivePadDefinition(u32 pinmux_name) {
|
||||
if (pinmux_name >= PinmuxDrivePadNameMax) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return &PinmuxDrivePadMap[pinmux_name];
|
||||
}
|
||||
|
||||
static uintptr_t GetPinmuxBaseAddress() {
|
||||
if (!g_initialized_pinmux_vaddr) {
|
||||
u64 vaddr;
|
||||
if (R_FAILED(svcQueryIoMapping(&vaddr, Boot::ApbMiscPhysicalBase, 0x4000))) {
|
||||
std::abort();
|
||||
}
|
||||
g_pinmux_vaddr = vaddr;
|
||||
g_initialized_pinmux_vaddr = true;
|
||||
}
|
||||
return g_pinmux_vaddr;
|
||||
}
|
||||
|
||||
u32 Boot::PinmuxUpdatePark(u32 pinmux_name) {
|
||||
const uintptr_t pinmux_base_vaddr = GetPinmuxBaseAddress();
|
||||
const PinmuxDefinition *pinmux_def = GetPinmuxDefinition(pinmux_name);
|
||||
|
||||
/* Fetch this PINMUX's register offset */
|
||||
u32 pinmux_reg_offset = pinmux_def->reg_offset;
|
||||
|
||||
/* Fetch this PINMUX's mask value */
|
||||
u32 pinmux_mask_val = pinmux_def->mask_val;
|
||||
|
||||
/* Get current register ptr. */
|
||||
volatile u32 *pinmux_reg = reinterpret_cast<volatile u32 *>(pinmux_base_vaddr + pinmux_reg_offset);
|
||||
|
||||
/* Read from the PINMUX register */
|
||||
u32 pinmux_val = *pinmux_reg;
|
||||
|
||||
/* This PINMUX supports park change */
|
||||
if (pinmux_mask_val & 0x20) {
|
||||
/* Clear park status if set */
|
||||
if (pinmux_val & 0x20) {
|
||||
pinmux_val &= ~(0x20);
|
||||
}
|
||||
}
|
||||
|
||||
/* Write to the appropriate PINMUX register */
|
||||
*pinmux_reg = pinmux_val;
|
||||
|
||||
/* Do a dummy read from the PINMUX register */
|
||||
pinmux_val = *pinmux_reg;
|
||||
|
||||
return pinmux_val;
|
||||
}
|
||||
|
||||
u32 Boot::PinmuxUpdatePad(u32 pinmux_name, u32 pinmux_config_val, u32 pinmux_config_mask_val) {
|
||||
const uintptr_t pinmux_base_vaddr = GetPinmuxBaseAddress();
|
||||
const PinmuxDefinition *pinmux_def = GetPinmuxDefinition(pinmux_name);
|
||||
|
||||
/* Fetch this PINMUX's register offset */
|
||||
u32 pinmux_reg_offset = pinmux_def->reg_offset;
|
||||
|
||||
/* Fetch this PINMUX's mask value */
|
||||
u32 pinmux_mask_val = pinmux_def->mask_val;
|
||||
|
||||
/* Get current register ptr. */
|
||||
volatile u32 *pinmux_reg = reinterpret_cast<volatile u32 *>(pinmux_base_vaddr + pinmux_reg_offset);
|
||||
|
||||
/* Read from the PINMUX register */
|
||||
u32 pinmux_val = *pinmux_reg;
|
||||
|
||||
/* This PINMUX register is locked */
|
||||
if (pinmux_val & 0x80) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u32 pm_val = (pinmux_config_val & 0x07);
|
||||
|
||||
/* Adjust PM */
|
||||
if (pinmux_config_mask_val & 0x07) {
|
||||
/* Apply additional changes first */
|
||||
if (pm_val == 0x05) {
|
||||
/* This pin supports PUPD change */
|
||||
if (pinmux_mask_val & 0x0C) {
|
||||
/* Change PUPD */
|
||||
if ((pinmux_val & 0x0C) != 0x04) {
|
||||
pinmux_val &= 0xFFFFFFF3;
|
||||
pinmux_val |= 0x04;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports Tristate change */
|
||||
if (pinmux_mask_val & 0x10) {
|
||||
/* Change Tristate */
|
||||
if (!(pinmux_val & 0x10)) {
|
||||
pinmux_val |= 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EInput change */
|
||||
if (pinmux_mask_val & 0x40) {
|
||||
/* Change EInput */
|
||||
if (pinmux_val & 0x40) {
|
||||
pinmux_val &= 0xFFFFFFBF;
|
||||
}
|
||||
}
|
||||
} else if (pm_val >= 0x06) {
|
||||
/* Default to safe value */
|
||||
pm_val = 0x04;
|
||||
}
|
||||
|
||||
/* Translate PM value if necessary */
|
||||
if (pm_val == 0x04 || pm_val == 0x05) {
|
||||
pm_val = pinmux_def->pm_val;
|
||||
}
|
||||
|
||||
/* This pin supports PM change */
|
||||
if (pinmux_mask_val & 0x03) {
|
||||
/* Change PM */
|
||||
if ((pinmux_val & 0x03) != (pm_val & 0x03)) {
|
||||
pinmux_val &= 0xFFFFFFFC;
|
||||
pinmux_val |= (pm_val & 0x03);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 pupd_config_val = (pinmux_config_val & 0x18);
|
||||
|
||||
/* Adjust PUPD */
|
||||
if (pinmux_config_mask_val & 0x18) {
|
||||
if (pupd_config_val < 0x11) {
|
||||
/* This pin supports PUPD change */
|
||||
if (pinmux_mask_val & 0x0C) {
|
||||
/* Change PUPD */
|
||||
if (((pinmux_val >> 0x02) & 0x03) != (pupd_config_val >> 0x03)) {
|
||||
pinmux_val &= 0xFFFFFFF3;
|
||||
pinmux_val |= (pupd_config_val >> 0x01);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 eod_config_val = (pinmux_config_val & 0x60);
|
||||
|
||||
/* Adjust EOd field */
|
||||
if (pinmux_config_mask_val & 0x60) {
|
||||
if (eod_config_val == 0x20) {
|
||||
/* This pin supports Tristate change */
|
||||
if (pinmux_mask_val & 0x10) {
|
||||
/* Change Tristate */
|
||||
if (!(pinmux_val & 0x10)) {
|
||||
pinmux_val |= 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EInput change */
|
||||
if (pinmux_mask_val & 0x40) {
|
||||
/* Change EInput */
|
||||
if (!(pinmux_val & 0x40)) {
|
||||
pinmux_val |= 0x40;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EOd change */
|
||||
if (pinmux_mask_val & 0x800) {
|
||||
/* Change EOd */
|
||||
if (pinmux_val & 0x800) {
|
||||
pinmux_val &= 0xFFFFF7FF;
|
||||
}
|
||||
}
|
||||
} else if (eod_config_val == 0x40) {
|
||||
/* This pin supports Tristate change */
|
||||
if (pinmux_mask_val & 0x10) {
|
||||
/* Change Tristate */
|
||||
if (pinmux_val & 0x10) {
|
||||
pinmux_val &= 0xFFFFFFEF;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EInput change */
|
||||
if (pinmux_mask_val & 0x40) {
|
||||
/* Change EInput */
|
||||
if (!(pinmux_val & 0x40)) {
|
||||
pinmux_val |= 0x40;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EOd change */
|
||||
if (pinmux_mask_val & 0x800) {
|
||||
/* Change EOd */
|
||||
if (pinmux_val & 0x800) {
|
||||
pinmux_val &= 0xFFFFF7FF;
|
||||
}
|
||||
}
|
||||
} else if (eod_config_val == 0x60) {
|
||||
/* This pin supports Tristate change */
|
||||
if (pinmux_mask_val & 0x10) {
|
||||
/* Change Tristate */
|
||||
if (pinmux_val & 0x10) {
|
||||
pinmux_val &= 0xFFFFFFEF;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EInput change */
|
||||
if (pinmux_mask_val & 0x40) {
|
||||
/* Change EInput */
|
||||
if (!(pinmux_val & 0x40)) {
|
||||
pinmux_val |= 0x40;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EOd change */
|
||||
if (pinmux_mask_val & 0x800) {
|
||||
/* Change EOd */
|
||||
if (!(pinmux_val & 0x800)) {
|
||||
pinmux_val |= 0x800;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* This pin supports Tristate change */
|
||||
if (pinmux_mask_val & 0x10) {
|
||||
/* Change Tristate */
|
||||
if (pinmux_val & 0x10) {
|
||||
pinmux_val &= 0xFFFFFFEF;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EInput change */
|
||||
if (pinmux_mask_val & 0x40) {
|
||||
/* Change EInput */
|
||||
if (pinmux_val & 0x40) {
|
||||
pinmux_val &= 0xFFFFFFBF;
|
||||
}
|
||||
}
|
||||
|
||||
/* This pin supports EOd change */
|
||||
if (pinmux_mask_val & 0x800) {
|
||||
/* Change EOd */
|
||||
if (pinmux_val & 0x800) {
|
||||
pinmux_val &= 0xFFFFF7FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 lock_config_val = (pinmux_config_val & 0x80);
|
||||
|
||||
/* Adjust Lock */
|
||||
if (pinmux_config_mask_val & 0x80) {
|
||||
/* This pin supports Lock change */
|
||||
if (pinmux_mask_val & 0x80) {
|
||||
/* Change Lock */
|
||||
if ((pinmux_val ^ pinmux_config_val) & 0x80) {
|
||||
pinmux_val &= 0xFFFFFF7F;
|
||||
pinmux_val |= lock_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 ioreset_config_val = (((pinmux_config_val >> 0x08) & 0x1) << 0x10);
|
||||
|
||||
/* Adjust IoReset */
|
||||
if (pinmux_config_mask_val & 0x100) {
|
||||
/* This pin supports IoReset change */
|
||||
if (pinmux_mask_val & 0x10000) {
|
||||
/* Change IoReset */
|
||||
if (((pinmux_val >> 0x10) ^ (pinmux_config_val >> 0x08)) & 0x01) {
|
||||
pinmux_val &= 0xFFFEFFFF;
|
||||
pinmux_val |= ioreset_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 park_config_val = (((pinmux_config_val >> 0x0A) & 0x1) << 0x5);
|
||||
|
||||
/* Adjust Park */
|
||||
if (pinmux_config_mask_val & 0x400) {
|
||||
/* This pin supports Park change */
|
||||
if (pinmux_mask_val & 0x20) {
|
||||
/* Change Park */
|
||||
if (((pinmux_val >> 0x05) ^ (pinmux_config_val >> 0x0A)) & 0x01) {
|
||||
pinmux_val &= 0xFFFFFFDF;
|
||||
pinmux_val |= park_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 elpdr_config_val = (((pinmux_config_val >> 0x0B) & 0x1) << 0x08);
|
||||
|
||||
/* Adjust ELpdr */
|
||||
if (pinmux_config_mask_val & 0x800) {
|
||||
/* This pin supports ELpdr change */
|
||||
if (pinmux_mask_val & 0x100) {
|
||||
/* Change ELpdr */
|
||||
if (((pinmux_val >> 0x08) ^ (pinmux_config_val >> 0x0B)) & 0x01) {
|
||||
pinmux_val &= 0xFFFFFEFF;
|
||||
pinmux_val |= elpdr_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 ehsm_config_val = (((pinmux_config_val >> 0x0C) & 0x1) << 0x09);
|
||||
|
||||
/* Adjust EHsm */
|
||||
if (pinmux_config_mask_val & 0x1000) {
|
||||
/* This pin supports EHsm change */
|
||||
if (pinmux_mask_val & 0x200) {
|
||||
/* Change EHsm */
|
||||
if (((pinmux_val >> 0x09) ^ (pinmux_config_val >> 0x0C)) & 0x01) {
|
||||
pinmux_val &= 0xFFFFFDFF;
|
||||
pinmux_val |= ehsm_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 eiohv_config_val = (((pinmux_config_val >> 0x09) & 0x1) << 0x0A);
|
||||
|
||||
/* Adjust EIoHv */
|
||||
if (pinmux_config_mask_val & 0x200) {
|
||||
/* This pin supports EIoHv change */
|
||||
if (pinmux_mask_val & 0x400) {
|
||||
/* Change EIoHv */
|
||||
if (((pinmux_val >> 0x0A) ^ (pinmux_config_val >> 0x09)) & 0x01) {
|
||||
pinmux_val &= 0xFFFFFBFF;
|
||||
pinmux_val |= eiohv_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 eschmt_config_val = (((pinmux_config_val >> 0x0D) & 0x1) << 0x0C);
|
||||
|
||||
/* Adjust ESchmt */
|
||||
if (pinmux_config_mask_val & 0x2000) {
|
||||
/* This pin supports ESchmt change */
|
||||
if (pinmux_mask_val & 0x1000) {
|
||||
/* Change ESchmt */
|
||||
if (((pinmux_val >> 0x0C) ^ (pinmux_config_val >> 0x0D)) & 0x01) {
|
||||
pinmux_val &= 0xFFFFEFFF;
|
||||
pinmux_val |= eschmt_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 preemp_config_val = (((pinmux_config_val >> 0x10) & 0x1) << 0xF);
|
||||
|
||||
/* Adjust Preemp */
|
||||
if (pinmux_config_mask_val & 0x10000) {
|
||||
/* This pin supports Preemp change */
|
||||
if (pinmux_mask_val & 0x8000) {
|
||||
/* Change Preemp */
|
||||
if (((pinmux_val >> 0x0F) ^ (pinmux_config_val >> 0x10)) & 0x01) {
|
||||
pinmux_val &= 0xFFFF7FFF;
|
||||
pinmux_val |= preemp_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 drvtype_config_val = (((pinmux_config_val >> 0x0E) & 0x3) << 0xD);
|
||||
|
||||
/* Adjust DrvType */
|
||||
if (pinmux_config_mask_val & 0xC000) {
|
||||
/* This pin supports DrvType change */
|
||||
if (pinmux_mask_val & 0x6000) {
|
||||
/* Change DrvType */
|
||||
if (((pinmux_val >> 0x0D) ^ (pinmux_config_val >> 0x0E)) & 0x03) {
|
||||
pinmux_val &= 0xFFFF9FFF;
|
||||
pinmux_val |= drvtype_config_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write to the appropriate PINMUX register */
|
||||
*pinmux_reg = pinmux_val;
|
||||
|
||||
/* Do a dummy read from the PINMUX register */
|
||||
pinmux_val = *pinmux_reg;
|
||||
|
||||
return pinmux_val;
|
||||
}
|
||||
|
||||
u32 Boot::PinmuxUpdateDrivePad(u32 pinmux_drivepad_name, u32 pinmux_drivepad_config_val, u32 pinmux_drivepad_config_mask_val) {
|
||||
const uintptr_t pinmux_base_vaddr = GetPinmuxBaseAddress();
|
||||
const PinmuxDrivePadDefinition *pinmux_drivepad_def = GetPinmuxDrivePadDefinition(pinmux_drivepad_name);
|
||||
|
||||
/* Fetch this PINMUX drive group's register offset */
|
||||
u32 pinmux_drivepad_reg_offset = pinmux_drivepad_def->reg_offset;
|
||||
|
||||
/* Fetch this PINMUX drive group's mask value */
|
||||
u32 pinmux_drivepad_mask_val = pinmux_drivepad_def->mask_val;
|
||||
|
||||
/* Get current register ptr. */
|
||||
volatile u32 *pinmux_drivepad_reg = reinterpret_cast<volatile u32 *>(pinmux_base_vaddr + pinmux_drivepad_reg_offset);
|
||||
|
||||
/* Read from the PINMUX drive group register */
|
||||
u32 pinmux_drivepad_val = *pinmux_drivepad_reg;
|
||||
|
||||
/* Adjust DriveDownStrength */
|
||||
if (pinmux_drivepad_config_mask_val & 0x1F000) {
|
||||
u32 mask_val = 0x7F000;
|
||||
|
||||
/* Adjust mask value */
|
||||
if ((pinmux_drivepad_mask_val & 0x7F000) != 0x7F000)
|
||||
mask_val = 0x1F000;
|
||||
|
||||
/* This drive group supports DriveDownStrength change */
|
||||
if (pinmux_drivepad_mask_val & mask_val) {
|
||||
/* Change DriveDownStrength */
|
||||
if (((pinmux_drivepad_config_val & 0x7F000) & mask_val) != (pinmux_drivepad_val & mask_val)) {
|
||||
pinmux_drivepad_val &= ~(mask_val);
|
||||
pinmux_drivepad_val |= ((pinmux_drivepad_config_val & 0x7F000) & mask_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust DriveUpStrength */
|
||||
if (pinmux_drivepad_config_mask_val & 0x1F00000) {
|
||||
u32 mask_val = 0x7F00000;
|
||||
|
||||
/* Adjust mask value */
|
||||
if ((pinmux_drivepad_mask_val & 0x7F00000) != 0x7F00000)
|
||||
mask_val = 0x1F00000;
|
||||
|
||||
/* This drive group supports DriveUpStrength change */
|
||||
if (pinmux_drivepad_mask_val & mask_val) {
|
||||
/* Change DriveUpStrength */
|
||||
if (((pinmux_drivepad_config_val & 0x7F00000) & mask_val) != (pinmux_drivepad_val & mask_val)) {
|
||||
pinmux_drivepad_val &= ~(mask_val);
|
||||
pinmux_drivepad_val |= ((pinmux_drivepad_config_val & 0x7F00000) & mask_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust DriveDownSlew */
|
||||
if (pinmux_drivepad_config_mask_val & 0x30000000) {
|
||||
/* This drive group supports DriveDownSlew change */
|
||||
if (pinmux_drivepad_mask_val & 0x30000000) {
|
||||
/* Change DriveDownSlew */
|
||||
if ((pinmux_drivepad_val ^ pinmux_drivepad_config_val) & 0x30000000) {
|
||||
pinmux_drivepad_val &= 0xCFFFFFFF;
|
||||
pinmux_drivepad_val |= (pinmux_drivepad_config_val & 0x30000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust DriveUpSlew */
|
||||
if (pinmux_drivepad_config_mask_val & 0xC0000000) {
|
||||
/* This drive group supports DriveUpSlew change */
|
||||
if (pinmux_drivepad_mask_val & 0xC0000000) {
|
||||
/* Change DriveUpSlew */
|
||||
if ((pinmux_drivepad_val ^ pinmux_drivepad_config_val) & 0xC0000000) {
|
||||
pinmux_drivepad_val &= 0x3FFFFFFF;
|
||||
pinmux_drivepad_val |= (pinmux_drivepad_config_val & 0xC0000000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write to the appropriate PINMUX drive group register */
|
||||
*pinmux_drivepad_reg = pinmux_drivepad_val;
|
||||
|
||||
/* Do a dummy read from the PINMUX drive group register */
|
||||
pinmux_drivepad_val = *pinmux_drivepad_reg;
|
||||
|
||||
return pinmux_drivepad_val;
|
||||
}
|
||||
|
||||
u32 Boot::PinmuxDummyReadDrivePad(u32 pinmux_drivepad_name) {
|
||||
const uintptr_t pinmux_base_vaddr = GetPinmuxBaseAddress();
|
||||
const PinmuxDrivePadDefinition *pinmux_drivepad_def = GetPinmuxDrivePadDefinition(pinmux_drivepad_name);
|
||||
|
||||
/* Fetch this PINMUX drive group's register offset */
|
||||
u32 pinmux_drivepad_reg_offset = pinmux_drivepad_def->reg_offset;
|
||||
|
||||
/* Get current register ptr. */
|
||||
volatile u32 *pinmux_drivepad_reg = reinterpret_cast<volatile u32 *>(pinmux_base_vaddr + pinmux_drivepad_reg_offset);
|
||||
|
||||
return *pinmux_drivepad_reg;
|
||||
}
|
@ -14,44 +14,54 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_pmc_wrapper.hpp"
|
||||
|
||||
static constexpr u32 SmcFunctionId_AtmosphereReadWriteRegister = 0xF0000002;
|
||||
namespace sts::boot {
|
||||
|
||||
static constexpr u32 PmcPhysStart = 0x7000E400;
|
||||
static constexpr u32 PmcPhysEnd = 0x7000EFFF;
|
||||
namespace {
|
||||
|
||||
static inline bool IsValidPmcAddress(u32 phys_addr) {
|
||||
return (phys_addr & 3) == 0 && PmcPhysStart <= phys_addr && phys_addr <= PmcPhysEnd;
|
||||
}
|
||||
/* Convenience definitions. */
|
||||
constexpr u32 SmcFunctionId_AtmosphereReadWriteRegister = 0xF0000002;
|
||||
|
||||
static inline u32 SmcAtmosphereReadWriteRegister(u32 phys_addr, u32 value, u32 mask) {
|
||||
SecmonArgs args;
|
||||
constexpr u32 PmcPhysStart = 0x7000E400;
|
||||
constexpr u32 PmcPhysEnd = 0x7000EFFF;
|
||||
|
||||
/* Helpers. */
|
||||
bool IsValidPmcAddress(u32 phys_addr) {
|
||||
return (phys_addr & 3) == 0 && PmcPhysStart <= phys_addr && phys_addr <= PmcPhysEnd;
|
||||
}
|
||||
|
||||
inline u32 SmcAtmosphereReadWriteRegister(u32 phys_addr, u32 value, u32 mask) {
|
||||
SecmonArgs args;
|
||||
|
||||
args.X[0] = SmcFunctionId_AtmosphereReadWriteRegister;
|
||||
args.X[1] = phys_addr;
|
||||
args.X[2] = mask;
|
||||
args.X[3] = value;
|
||||
R_ASSERT(svcCallSecureMonitor(&args));
|
||||
if (args.X[0] != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return static_cast<u32>(args.X[1]);
|
||||
}
|
||||
|
||||
args.X[0] = SmcFunctionId_AtmosphereReadWriteRegister;
|
||||
args.X[1] = phys_addr;
|
||||
args.X[2] = mask;
|
||||
args.X[3] = value;
|
||||
svcCallSecureMonitor(&args);
|
||||
if (args.X[0] != 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return static_cast<u32>(args.X[1]);
|
||||
}
|
||||
u32 ReadPmcRegister(u32 phys_addr) {
|
||||
if (!IsValidPmcAddress(phys_addr)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u32 Boot::ReadPmcRegister(u32 phys_addr) {
|
||||
if (!IsValidPmcAddress(phys_addr)) {
|
||||
std::abort();
|
||||
return SmcAtmosphereReadWriteRegister(phys_addr, 0, 0);
|
||||
}
|
||||
|
||||
return SmcAtmosphereReadWriteRegister(phys_addr, 0, 0);
|
||||
}
|
||||
void WritePmcRegister(u32 phys_addr, u32 value, u32 mask) {
|
||||
if (!IsValidPmcAddress(phys_addr)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void Boot::WritePmcRegister(u32 phys_addr, u32 value, u32 mask) {
|
||||
if (!IsValidPmcAddress(phys_addr)) {
|
||||
std::abort();
|
||||
SmcAtmosphereReadWriteRegister(phys_addr, value, mask);
|
||||
}
|
||||
|
||||
SmcAtmosphereReadWriteRegister(phys_addr, value, mask);
|
||||
}
|
||||
|
27
stratosphere/boot/source/boot_pmc_wrapper.hpp
Normal file
27
stratosphere/boot/source/boot_pmc_wrapper.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
/* PMC Access Utilities. */
|
||||
u32 ReadPmcRegister(u32 phys_addr);
|
||||
void WritePmcRegister(u32 phys_addr, u32 value, u32 mask = UINT32_MAX);
|
||||
|
||||
}
|
@ -16,110 +16,103 @@
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "boot_functions.hpp"
|
||||
|
||||
#include "boot_i2c_utils.hpp"
|
||||
#include "boot_pmic_driver.hpp"
|
||||
|
||||
void PmicDriver::ShutdownSystem() {
|
||||
if (R_FAILED(this->ShutdownSystem(false))) {
|
||||
std::abort();
|
||||
namespace sts::boot {
|
||||
|
||||
void PmicDriver::ShutdownSystem() {
|
||||
R_ASSERT(this->ShutdownSystem(false));
|
||||
}
|
||||
}
|
||||
|
||||
void PmicDriver::RebootSystem() {
|
||||
if (R_FAILED(this->ShutdownSystem(true))) {
|
||||
std::abort();
|
||||
void PmicDriver::RebootSystem() {
|
||||
R_ASSERT(this->ShutdownSystem(true));
|
||||
}
|
||||
}
|
||||
|
||||
Result PmicDriver::GetAcOk(bool *out) {
|
||||
u8 power_status;
|
||||
R_TRY(this->GetPowerStatus(&power_status));
|
||||
*out = (power_status & 0x02) != 0;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result PmicDriver::GetPowerIntr(u8 *out) {
|
||||
const u8 addr = 0x0B;
|
||||
return Boot::ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result PmicDriver::GetPowerStatus(u8 *out) {
|
||||
const u8 addr = 0x15;
|
||||
return Boot::ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result PmicDriver::GetNvErc(u8 *out) {
|
||||
const u8 addr = 0x0C;
|
||||
return Boot::ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result PmicDriver::GetPowerButtonPressed(bool *out) {
|
||||
u8 power_intr;
|
||||
R_TRY(this->GetPowerIntr(&power_intr));
|
||||
*out = (power_intr & 0x08) != 0;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result PmicDriver::ShutdownSystem(bool reboot) {
|
||||
const u8 on_off_1_addr = 0x41;
|
||||
const u8 on_off_2_addr = 0x42;
|
||||
|
||||
/* Get value, set or clear software reset mask. */
|
||||
u8 on_off_2_val = 0;
|
||||
if (R_FAILED(Boot::ReadI2cRegister(this->i2c_session, &on_off_2_val, sizeof(on_off_2_val), &on_off_2_addr, sizeof(on_off_2_addr)))) {
|
||||
std::abort();
|
||||
Result PmicDriver::GetAcOk(bool *out) {
|
||||
u8 power_status;
|
||||
R_TRY(this->GetPowerStatus(&power_status));
|
||||
*out = (power_status & 0x02) != 0;
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (reboot) {
|
||||
on_off_2_val |= 0x80;
|
||||
} else {
|
||||
on_off_2_val &= ~0x80;
|
||||
|
||||
Result PmicDriver::GetPowerIntr(u8 *out) {
|
||||
const u8 addr = 0x0B;
|
||||
return ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
if (R_FAILED(Boot::WriteI2cRegister(this->i2c_session, &on_off_2_val, sizeof(on_off_2_val), &on_off_2_addr, sizeof(on_off_2_addr)))) {
|
||||
|
||||
Result PmicDriver::GetPowerStatus(u8 *out) {
|
||||
const u8 addr = 0x15;
|
||||
return ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result PmicDriver::GetNvErc(u8 *out) {
|
||||
const u8 addr = 0x0C;
|
||||
return ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result PmicDriver::GetPowerButtonPressed(bool *out) {
|
||||
u8 power_intr;
|
||||
R_TRY(this->GetPowerIntr(&power_intr));
|
||||
*out = (power_intr & 0x08) != 0;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result PmicDriver::ShutdownSystem(bool reboot) {
|
||||
const u8 on_off_1_addr = 0x41;
|
||||
const u8 on_off_2_addr = 0x42;
|
||||
|
||||
/* Get value, set or clear software reset mask. */
|
||||
u8 on_off_2_val = 0;
|
||||
R_ASSERT(ReadI2cRegister(this->i2c_session, &on_off_2_val, sizeof(on_off_2_val), &on_off_2_addr, sizeof(on_off_2_addr)));
|
||||
if (reboot) {
|
||||
on_off_2_val |= 0x80;
|
||||
} else {
|
||||
on_off_2_val &= ~0x80;
|
||||
}
|
||||
R_ASSERT(WriteI2cRegister(this->i2c_session, &on_off_2_val, sizeof(on_off_2_val), &on_off_2_addr, sizeof(on_off_2_addr)));
|
||||
|
||||
/* Get value, set software reset mask. */
|
||||
u8 on_off_1_val = 0;
|
||||
R_ASSERT(ReadI2cRegister(this->i2c_session, &on_off_1_val, sizeof(on_off_1_val), &on_off_1_addr, sizeof(on_off_1_addr)));
|
||||
on_off_1_val |= 0x80;
|
||||
|
||||
/* Finalize the battery. */
|
||||
{
|
||||
BatteryDriver battery_driver;
|
||||
this->FinalizeBattery(&battery_driver);
|
||||
}
|
||||
|
||||
/* Actually write the value to trigger shutdown/reset. */
|
||||
R_ASSERT(WriteI2cRegister(this->i2c_session, &on_off_1_val, sizeof(on_off_1_val), &on_off_1_addr, sizeof(on_off_1_addr)));
|
||||
|
||||
/* Allow up to 5 seconds for shutdown/reboot to take place. */
|
||||
svcSleepThread(5'000'000'000ul);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Get value, set software reset mask. */
|
||||
u8 on_off_1_val = 0;
|
||||
if (R_FAILED(Boot::ReadI2cRegister(this->i2c_session, &on_off_1_val, sizeof(on_off_1_val), &on_off_1_addr, sizeof(on_off_1_addr)))) {
|
||||
std::abort();
|
||||
}
|
||||
on_off_1_val |= 0x80;
|
||||
void PmicDriver::FinalizeBattery(BatteryDriver *battery_driver) {
|
||||
/* Set shutdown timer. */
|
||||
battery_driver->SetShutdownTimer();
|
||||
|
||||
/* Finalize the battery. */
|
||||
{
|
||||
BatteryDriver battery_driver;
|
||||
this->FinalizeBattery(&battery_driver);
|
||||
/* Get whether shutdown is enabled. */
|
||||
bool shutdown_enabled;
|
||||
if (R_FAILED(battery_driver->GetShutdownEnabled(&shutdown_enabled))) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ac_ok;
|
||||
bool desired_shutdown_enabled;
|
||||
if (R_FAILED(this->GetAcOk(&ac_ok)) || ac_ok) {
|
||||
desired_shutdown_enabled = false;
|
||||
} else {
|
||||
desired_shutdown_enabled = true;
|
||||
}
|
||||
|
||||
if (shutdown_enabled != desired_shutdown_enabled) {
|
||||
battery_driver->SetShutdownEnabled(desired_shutdown_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/* Actually write the value to trigger shutdown/reset. */
|
||||
if (R_FAILED(Boot::WriteI2cRegister(this->i2c_session, &on_off_1_val, sizeof(on_off_1_val), &on_off_1_addr, sizeof(on_off_1_addr)))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Allow up to 5 seconds for shutdown/reboot to take place. */
|
||||
svcSleepThread(5'000'000'000ul);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void PmicDriver::FinalizeBattery(BatteryDriver *battery_driver) {
|
||||
/* Set shutdown timer. */
|
||||
battery_driver->SetShutdownTimer();
|
||||
|
||||
/* Get whether shutdown is enabled. */
|
||||
bool shutdown_enabled;
|
||||
if (R_FAILED(battery_driver->GetShutdownEnabled(&shutdown_enabled))) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ac_ok;
|
||||
bool desired_shutdown_enabled;
|
||||
if (R_FAILED(this->GetAcOk(&ac_ok)) || ac_ok) {
|
||||
desired_shutdown_enabled = false;
|
||||
} else {
|
||||
desired_shutdown_enabled = true;
|
||||
}
|
||||
|
||||
if (shutdown_enabled != desired_shutdown_enabled) {
|
||||
battery_driver->SetShutdownEnabled(desired_shutdown_enabled);
|
||||
}
|
||||
}
|
||||
|
@ -18,31 +18,36 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_driver/i2c_api.hpp"
|
||||
#include "boot_battery_driver.hpp"
|
||||
|
||||
class PmicDriver {
|
||||
private:
|
||||
I2cSessionImpl i2c_session;
|
||||
public:
|
||||
PmicDriver() {
|
||||
I2cDriver::Initialize();
|
||||
I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Max77620Pmic);
|
||||
}
|
||||
namespace sts::boot {
|
||||
|
||||
/* Driver object. */
|
||||
class PmicDriver {
|
||||
private:
|
||||
i2c::driver::Session i2c_session;
|
||||
public:
|
||||
PmicDriver() {
|
||||
i2c::driver::Initialize();
|
||||
i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Max77620Pmic);
|
||||
}
|
||||
|
||||
~PmicDriver() {
|
||||
i2c::driver::CloseSession(this->i2c_session);
|
||||
i2c::driver::Finalize();
|
||||
}
|
||||
private:
|
||||
Result GetPowerStatus(u8 *out);
|
||||
Result ShutdownSystem(bool reboot);
|
||||
void FinalizeBattery(BatteryDriver *battery_driver);
|
||||
public:
|
||||
void ShutdownSystem();
|
||||
void RebootSystem();
|
||||
Result GetAcOk(bool *out);
|
||||
Result GetPowerIntr(u8 *out);
|
||||
Result GetNvErc(u8 *out);
|
||||
Result GetPowerButtonPressed(bool *out);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
~PmicDriver() {
|
||||
I2cDriver::CloseSession(this->i2c_session);
|
||||
I2cDriver::Finalize();
|
||||
}
|
||||
private:
|
||||
Result GetPowerStatus(u8 *out);
|
||||
Result ShutdownSystem(bool reboot);
|
||||
void FinalizeBattery(BatteryDriver *battery_driver);
|
||||
public:
|
||||
void ShutdownSystem();
|
||||
void RebootSystem();
|
||||
Result GetAcOk(bool *out);
|
||||
Result GetPowerIntr(u8 *out);
|
||||
Result GetNvErc(u8 *out);
|
||||
Result GetPowerButtonPressed(bool *out);
|
||||
};
|
79
stratosphere/boot/source/boot_power_utils.cpp
Normal file
79
stratosphere/boot/source/boot_power_utils.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <strings.h>
|
||||
|
||||
#include "boot_power_utils.hpp"
|
||||
#include "fusee-primary_bin.h"
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr uintptr_t IramBase = 0x40000000ull;
|
||||
constexpr uintptr_t IramPayloadBase = 0x40010000ull;
|
||||
constexpr size_t IramSize = 0x40000;
|
||||
constexpr size_t IramPayloadMaxSize = 0x2E000;
|
||||
|
||||
/* Globals. */
|
||||
u8 __attribute__ ((aligned (0x1000))) g_work_page[0x1000];
|
||||
|
||||
/* Helpers. */
|
||||
void ClearIram() {
|
||||
/* Make page FFs. */
|
||||
memset(g_work_page, 0xFF, sizeof(g_work_page));
|
||||
|
||||
/* Overwrite all of IRAM with FFs. */
|
||||
for (size_t ofs = 0; ofs < IramSize; ofs += sizeof(g_work_page)) {
|
||||
CopyToIram(IramBase + ofs, g_work_page, sizeof(g_work_page));
|
||||
}
|
||||
}
|
||||
|
||||
void DoRebootToPayload(AtmosphereFatalErrorContext *ctx) {
|
||||
/* Ensure clean IRAM state. */
|
||||
ClearIram();
|
||||
|
||||
/* Copy in payload. */
|
||||
for (size_t ofs = 0; ofs < fusee_primary_bin_size; ofs += 0x1000) {
|
||||
std::memcpy(g_work_page, &fusee_primary_bin[ofs], std::min(static_cast<size_t>(fusee_primary_bin_size - ofs), size_t(0x1000)));
|
||||
CopyToIram(IramPayloadBase + ofs, g_work_page, 0x1000);
|
||||
}
|
||||
|
||||
|
||||
/* Copy in fatal error context, if relevant. */
|
||||
if (ctx != nullptr) {
|
||||
std::memset(g_work_page, 0xCC, sizeof(g_work_page));
|
||||
std::memcpy(g_work_page, ctx, sizeof(*ctx));
|
||||
CopyToIram(IramPayloadBase + IramPayloadMaxSize, g_work_page, sizeof(g_work_page));
|
||||
}
|
||||
|
||||
RebootToIramPayload();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RebootSystem() {
|
||||
DoRebootToPayload(nullptr);
|
||||
}
|
||||
|
||||
void RebootForFatalError(AtmosphereFatalErrorContext *ctx) {
|
||||
DoRebootToPayload(ctx);
|
||||
}
|
||||
|
||||
}
|
@ -18,27 +18,13 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
enum HardwareType {
|
||||
HardwareType_Icosa = 0,
|
||||
HardwareType_Copper = 1,
|
||||
HardwareType_Hoag = 2,
|
||||
HardwareType_Iowa = 3,
|
||||
};
|
||||
namespace sts::boot {
|
||||
|
||||
struct GpioInitialConfig {
|
||||
u32 pad_name;
|
||||
GpioDirection direction;
|
||||
GpioValue value;
|
||||
};
|
||||
/* Power utilities. */
|
||||
void RebootSystem();
|
||||
void ShutdownSystem();
|
||||
|
||||
struct PinmuxInitialConfig {
|
||||
u32 name;
|
||||
u32 val;
|
||||
u32 mask;
|
||||
};
|
||||
/* Atmosphere power utilities. */
|
||||
void RebootForFatalError(AtmosphereFatalErrorContext *ctx);
|
||||
|
||||
struct WakePinConfig {
|
||||
u32 index;
|
||||
bool enabled;
|
||||
u32 level;
|
||||
};
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <strings.h>
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_reboot_manager.hpp"
|
||||
#include "fusee-primary_bin.h"
|
||||
|
||||
static u8 g_work_page[0x1000] __attribute__ ((aligned (0x1000)));
|
||||
|
||||
static void ClearIram() {
|
||||
/* Make page FFs. */
|
||||
memset(g_work_page, 0xFF, sizeof(g_work_page));
|
||||
|
||||
/* Overwrite all of IRAM with FFs. */
|
||||
for (size_t ofs = 0; ofs < IRAM_SIZE; ofs += sizeof(g_work_page)) {
|
||||
CopyToIram(IRAM_BASE + ofs, g_work_page, sizeof(g_work_page));
|
||||
}
|
||||
}
|
||||
|
||||
static void DoRebootToPayload() {
|
||||
/* Ensure clean IRAM state. */
|
||||
ClearIram();
|
||||
|
||||
/* Copy in payload. */
|
||||
for (size_t ofs = 0; ofs < fusee_primary_bin_size; ofs += 0x1000) {
|
||||
std::memcpy(g_work_page, &fusee_primary_bin[ofs], std::min(static_cast<size_t>(fusee_primary_bin_size - ofs), size_t(0x1000)));
|
||||
CopyToIram(IRAM_PAYLOAD_BASE + ofs, g_work_page, 0x1000);
|
||||
}
|
||||
|
||||
RebootToIramPayload();
|
||||
}
|
||||
|
||||
Result BootRebootManager::PerformReboot() {
|
||||
DoRebootToPayload();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void BootRebootManager::RebootForFatalError(AtmosphereFatalErrorContext *ctx) {
|
||||
/* Ensure clean IRAM state. */
|
||||
ClearIram();
|
||||
|
||||
/* Copy in payload. */
|
||||
for (size_t ofs = 0; ofs < fusee_primary_bin_size; ofs += 0x1000) {
|
||||
std::memcpy(g_work_page, &fusee_primary_bin[ofs], std::min(static_cast<size_t>(fusee_primary_bin_size - ofs), size_t(0x1000)));
|
||||
CopyToIram(IRAM_PAYLOAD_BASE + ofs, g_work_page, 0x1000);
|
||||
}
|
||||
|
||||
std::memset(g_work_page, 0xCC, sizeof(g_work_page));
|
||||
std::memcpy(g_work_page, ctx, sizeof(*ctx));
|
||||
CopyToIram(IRAM_PAYLOAD_BASE + IRAM_PAYLOAD_MAX_SIZE, g_work_page, sizeof(g_work_page));
|
||||
|
||||
RebootToIramPayload();
|
||||
}
|
||||
|
||||
void Boot::RebootSystem() {
|
||||
if (R_FAILED(BootRebootManager::PerformReboot())) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
@ -17,8 +17,6 @@
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr size_t CLK_RST_CONTROLLER_RST_SOURCE = 0x0;
|
||||
static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_L = 0x4;
|
||||
static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_H = 0x8;
|
||||
|
@ -19,8 +19,6 @@
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
#define DC_CMD_GENERAL_INCR_SYNCPT 0x00
|
||||
|
||||
#define DC_CMD_GENERAL_INCR_SYNCPT_CNTRL 0x01
|
||||
|
@ -17,8 +17,6 @@
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr size_t GPIO_PORT3_CNF_0 = 0x200;
|
||||
static constexpr size_t GPIO_PORT3_OE_0 = 0x210;
|
||||
static constexpr size_t GPIO_PORT3_OUT_0 = 0x220;
|
||||
|
@ -17,8 +17,6 @@
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr size_t APB_MISC_GP_SDMMC1_CLK_LPBK_CONTROL = 0x8D4;
|
||||
static constexpr size_t APB_MISC_GP_SDMMC3_CLK_LPBK_CONTROL = 0x8D8;
|
||||
static constexpr size_t APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL = 0xA98;
|
||||
|
@ -17,8 +17,6 @@
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr uintptr_t PmcBase = 0x7000E400ul;
|
||||
|
||||
static constexpr size_t APBDEV_PMC_CNTRL = 0x0;
|
||||
|
@ -14,17 +14,29 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "updater/updater_api.hpp"
|
||||
#include <stratosphere/spl.hpp>
|
||||
#include <stratosphere/updater.hpp>
|
||||
|
||||
static u8 __attribute__((__aligned__(0x1000))) g_boot_image_work_buffer[0x10000];
|
||||
#include "boot_power_utils.hpp"
|
||||
#include "boot_repair_boot_images.hpp"
|
||||
|
||||
void Boot::CheckAndRepairBootImages() {
|
||||
const BootImageUpdateType boot_image_update_type = Updater::GetBootImageUpdateType(Boot::GetHardwareType());
|
||||
namespace sts::boot {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Globals. */
|
||||
u8 __attribute__((aligned(0x1000))) g_boot_image_work_buffer[0x10000];
|
||||
|
||||
bool repaired_normal, repaired_safe;
|
||||
if (R_SUCCEEDED(Updater::VerifyBootImagesAndRepairIfNeeded(&repaired_normal, &repaired_safe, g_boot_image_work_buffer, sizeof(g_boot_image_work_buffer), boot_image_update_type)) && repaired_normal) {
|
||||
/* Nintendo only performs a reboot on successful normal repair. */
|
||||
Boot::RebootSystem();
|
||||
}
|
||||
|
||||
void CheckAndRepairBootImages() {
|
||||
const auto boot_image_update_type = updater::GetBootImageUpdateType(spl::GetHardwareType());
|
||||
|
||||
bool repaired_normal, repaired_safe;
|
||||
if (R_SUCCEEDED(updater::VerifyBootImagesAndRepairIfNeeded(&repaired_normal, &repaired_safe, g_boot_image_work_buffer, sizeof(g_boot_image_work_buffer), boot_image_update_type)) && repaired_normal) {
|
||||
/* Nintendo only performs a reboot on successful normal repair. */
|
||||
RebootSystem();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
25
stratosphere/boot/source/boot_repair_boot_images.hpp
Normal file
25
stratosphere/boot/source/boot_repair_boot_images.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
void CheckAndRepairBootImages();
|
||||
|
||||
}
|
@ -16,23 +16,27 @@
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include "boot_functions.hpp"
|
||||
|
||||
#include "boot_rtc_driver.hpp"
|
||||
|
||||
Result RtcDriver::ReadRtcRegister(u8 *out, u8 address) {
|
||||
const u8 update_addr = 0x04;
|
||||
const u8 update_val = 0x10;
|
||||
R_TRY(Boot::WriteI2cRegister(this->i2c_session, &update_val, sizeof(update_val), &update_addr, sizeof(update_addr)));
|
||||
svcSleepThread(16'000'000ul);
|
||||
return Boot::ReadI2cRegister(this->i2c_session, out, sizeof(*out), &address, sizeof(address));
|
||||
}
|
||||
namespace sts::boot {
|
||||
|
||||
Result RtcDriver::GetRtcIntr(u8 *out) {
|
||||
const u8 addr = 0x00;
|
||||
return Boot::ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
Result RtcDriver::ReadRtcRegister(u8 *out, u8 address) {
|
||||
const u8 update_addr = 0x04;
|
||||
const u8 update_val = 0x10;
|
||||
R_TRY(WriteI2cRegister(this->i2c_session, &update_val, sizeof(update_val), &update_addr, sizeof(update_addr)));
|
||||
svcSleepThread(16'000'000ul);
|
||||
return ReadI2cRegister(this->i2c_session, out, sizeof(*out), &address, sizeof(address));
|
||||
}
|
||||
|
||||
Result RtcDriver::GetRtcIntr(u8 *out) {
|
||||
const u8 addr = 0x00;
|
||||
return ReadI2cRegister(this->i2c_session, out, sizeof(*out), &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
Result RtcDriver::GetRtcIntrM(u8 *out) {
|
||||
const u8 addr = 0x01;
|
||||
return this->ReadRtcRegister(out, addr);
|
||||
}
|
||||
|
||||
Result RtcDriver::GetRtcIntrM(u8 *out) {
|
||||
const u8 addr = 0x01;
|
||||
return this->ReadRtcRegister(out, addr);
|
||||
}
|
||||
|
@ -18,24 +18,28 @@
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_driver/i2c_api.hpp"
|
||||
#include "boot_i2c_utils.hpp"
|
||||
|
||||
class RtcDriver {
|
||||
private:
|
||||
I2cSessionImpl i2c_session;
|
||||
public:
|
||||
RtcDriver() {
|
||||
I2cDriver::Initialize();
|
||||
I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Max77620Rtc);
|
||||
}
|
||||
namespace sts::boot {
|
||||
|
||||
~RtcDriver() {
|
||||
I2cDriver::CloseSession(this->i2c_session);
|
||||
I2cDriver::Finalize();
|
||||
}
|
||||
private:
|
||||
Result ReadRtcRegister(u8 *out, u8 address);
|
||||
public:
|
||||
Result GetRtcIntr(u8 *out);
|
||||
Result GetRtcIntrM(u8 *out);
|
||||
};
|
||||
class RtcDriver {
|
||||
private:
|
||||
i2c::driver::Session i2c_session;
|
||||
public:
|
||||
RtcDriver() {
|
||||
i2c::driver::Initialize();
|
||||
i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Max77620Rtc);
|
||||
}
|
||||
|
||||
~RtcDriver() {
|
||||
i2c::driver::CloseSession(this->i2c_session);
|
||||
i2c::driver::Finalize();
|
||||
}
|
||||
private:
|
||||
Result ReadRtcRegister(u8 *out, u8 address);
|
||||
public:
|
||||
Result GetRtcIntr(u8 *out);
|
||||
Result GetRtcIntrM(u8 *out);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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_functions.hpp"
|
||||
|
||||
HardwareType Boot::GetHardwareType() {
|
||||
u64 out_val = 0;
|
||||
if (R_FAILED(splGetConfig(SplConfigItem_HardwareType, &out_val))) {
|
||||
std::abort();
|
||||
}
|
||||
return static_cast<HardwareType>(out_val);
|
||||
}
|
||||
|
||||
bool Boot::IsRecoveryBoot() {
|
||||
u64 val = 0;
|
||||
if (R_FAILED(splGetConfig(SplConfigItem_IsRecoveryBoot, &val))) {
|
||||
std::abort();
|
||||
}
|
||||
return val != 0;
|
||||
}
|
||||
|
||||
bool Boot::IsMariko() {
|
||||
HardwareType hw_type = GetHardwareType();
|
||||
switch (hw_type) {
|
||||
case HardwareType_Icosa:
|
||||
case HardwareType_Copper:
|
||||
return false;
|
||||
case HardwareType_Hoag:
|
||||
case HardwareType_Iowa:
|
||||
return true;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
@ -14,21 +14,34 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include "boot_splash_screen_notext.hpp"
|
||||
#include "boot_boot_reason.hpp"
|
||||
#include "boot_display.hpp"
|
||||
#include "boot_splash_screen.hpp"
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Include splash screen into anonymous namespace. */
|
||||
/* TODO: Compile-time switch for splash_screen_text.hpp? */
|
||||
#include "boot_splash_screen_notext.inc"
|
||||
|
||||
void Boot::ShowSplashScreen() {
|
||||
const u32 boot_reason = Boot::GetBootReason();
|
||||
if (boot_reason == 1 || boot_reason == 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
Boot::InitializeDisplay();
|
||||
{
|
||||
/* Splash screen is shown for 2 seconds. */
|
||||
Boot::ShowDisplay(SplashScreenX, SplashScreenY, SplashScreenW, SplashScreenH, SplashScreen);
|
||||
svcSleepThread(2'000'000'000ul);
|
||||
void ShowSplashScreen() {
|
||||
const u32 boot_reason = GetBootReason();
|
||||
if (boot_reason == 1 || boot_reason == 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeDisplay();
|
||||
{
|
||||
/* Splash screen is shown for 2 seconds. */
|
||||
ShowDisplay(SplashScreenX, SplashScreenY, SplashScreenW, SplashScreenH, SplashScreen);
|
||||
svcSleepThread(2'000'000'000ul);
|
||||
}
|
||||
FinalizeDisplay();
|
||||
}
|
||||
Boot::FinalizeDisplay();
|
||||
|
||||
}
|
||||
|
||||
|
25
stratosphere/boot/source/boot_splash_screen.hpp
Normal file
25
stratosphere/boot/source/boot_splash_screen.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
void ShowSplashScreen();
|
||||
|
||||
}
|
File diff suppressed because one or more lines are too long
22
stratosphere/boot/source/boot_splash_screen_notext.inc
Normal file
22
stratosphere/boot/source/boot_splash_screen_notext.inc
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
22
stratosphere/boot/source/boot_splash_screen_text.inc
Normal file
22
stratosphere/boot/source/boot_splash_screen_text.inc
Normal file
File diff suppressed because one or more lines are too long
@ -14,19 +14,13 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
#include "boot_registers_pmc.hpp"
|
||||
|
||||
struct WakeControlConfig {
|
||||
u32 reg_offset;
|
||||
u32 mask_val;
|
||||
bool flag_val;
|
||||
};
|
||||
|
||||
static constexpr WakeControlConfig WakeControlConfigs[] = {
|
||||
constexpr WakeControlConfig WakeControlConfigs[] = {
|
||||
{APBDEV_PMC_CNTRL, 0x0800, true},
|
||||
{APBDEV_PMC_CNTRL, 0x0400, false},
|
||||
{APBDEV_PMC_CNTRL, 0x0200, true},
|
||||
@ -38,4 +32,4 @@ static constexpr WakeControlConfig WakeControlConfigs[] = {
|
||||
{APBDEV_PMC_CNTRL2, 0x0001, true},
|
||||
};
|
||||
|
||||
static constexpr size_t NumWakeControlConfigs = sizeof(WakeControlConfigs) / sizeof(WakeControlConfigs[0]);
|
||||
constexpr size_t NumWakeControlConfigs = sizeof(WakeControlConfigs) / sizeof(WakeControlConfigs[0]);
|
@ -14,11 +14,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr WakePinConfig WakePinConfigs[] = {
|
||||
{0x00, false, 0x02},
|
||||
{0x01, false, 0x02},
|
@ -14,11 +14,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr WakePinConfig WakePinConfigsCopper[] = {
|
||||
{0x00, true, 0x02},
|
||||
{0x01, false, 0x02},
|
@ -14,75 +14,100 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "boot_functions.hpp"
|
||||
#include <stratosphere/spl.hpp>
|
||||
|
||||
#include "boot_pmc_wrapper.hpp"
|
||||
#include "boot_wake_pins.hpp"
|
||||
|
||||
#include "boot_registers_pmc.hpp"
|
||||
#include "boot_wake_control_configs.hpp"
|
||||
#include "boot_wake_pin_configuration.hpp"
|
||||
#include "boot_wake_pin_configuration_copper.hpp"
|
||||
|
||||
static void UpdatePmcControlBit(const u32 reg_offset, const u32 mask_val, const bool flag) {
|
||||
Boot::WritePmcRegister(PmcBase + reg_offset, flag ? UINT32_MAX : 0, mask_val);
|
||||
Boot::ReadPmcRegister(PmcBase + reg_offset);
|
||||
}
|
||||
namespace sts::boot {
|
||||
|
||||
static void InitializePmcWakeConfiguration(const bool is_blink) {
|
||||
/* Initialize APBDEV_PMC_WAKE_DEBOUNCE_EN, do a dummy read. */
|
||||
Boot::WritePmcRegister(PmcBase + APBDEV_PMC_WAKE_DEBOUNCE_EN, 0);
|
||||
Boot::ReadPmcRegister(PmcBase + APBDEV_PMC_WAKE_DEBOUNCE_EN);
|
||||
/* Include configuration into anonymous namespace. */
|
||||
namespace {
|
||||
|
||||
/* Initialize APBDEV_PMC_BLINK_TIMER, do a dummy read. */
|
||||
Boot::WritePmcRegister(PmcBase + APBDEV_PMC_BLINK_TIMER, 0x8008800);
|
||||
Boot::ReadPmcRegister(PmcBase + APBDEV_PMC_BLINK_TIMER);
|
||||
struct WakePinConfig {
|
||||
u32 index;
|
||||
bool enabled;
|
||||
u32 level;
|
||||
};
|
||||
|
||||
#include "boot_wake_control_configs.inc"
|
||||
#include "boot_wake_pin_configuration.inc"
|
||||
#include "boot_wake_pin_configuration_copper.inc"
|
||||
|
||||
/* Set control bits, do dummy reads. */
|
||||
for (size_t i = 0; i < NumWakeControlConfigs; i++) {
|
||||
UpdatePmcControlBit(WakeControlConfigs[i].reg_offset, WakeControlConfigs[i].mask_val, WakeControlConfigs[i].flag_val);
|
||||
}
|
||||
|
||||
/* Set bit 0x80 in APBDEV_PMC_CNTRL based on is_blink, do dummy read. */
|
||||
UpdatePmcControlBit(APBDEV_PMC_CNTRL, 0x80, is_blink);
|
||||
namespace {
|
||||
|
||||
/* Set bit 0x100000 in APBDEV_PMC_DPD_PADS_ORIDE based on is_blink, do dummy read. */
|
||||
UpdatePmcControlBit(APBDEV_PMC_DPD_PADS_ORIDE, 0x100000, is_blink);
|
||||
}
|
||||
/* Helpers. */
|
||||
void UpdatePmcControlBit(const u32 reg_offset, const u32 mask_val, const bool flag) {
|
||||
WritePmcRegister(PmcBase + reg_offset, flag ? UINT32_MAX : 0, mask_val);
|
||||
ReadPmcRegister(PmcBase + reg_offset);
|
||||
}
|
||||
|
||||
void InitializePmcWakeConfiguration(const bool is_blink) {
|
||||
/* Initialize APBDEV_PMC_WAKE_DEBOUNCE_EN, do a dummy read. */
|
||||
WritePmcRegister(PmcBase + APBDEV_PMC_WAKE_DEBOUNCE_EN, 0);
|
||||
ReadPmcRegister(PmcBase + APBDEV_PMC_WAKE_DEBOUNCE_EN);
|
||||
|
||||
/* Initialize APBDEV_PMC_BLINK_TIMER, do a dummy read. */
|
||||
WritePmcRegister(PmcBase + APBDEV_PMC_BLINK_TIMER, 0x8008800);
|
||||
ReadPmcRegister(PmcBase + APBDEV_PMC_BLINK_TIMER);
|
||||
|
||||
/* Set control bits, do dummy reads. */
|
||||
for (size_t i = 0; i < NumWakeControlConfigs; i++) {
|
||||
UpdatePmcControlBit(WakeControlConfigs[i].reg_offset, WakeControlConfigs[i].mask_val, WakeControlConfigs[i].flag_val);
|
||||
}
|
||||
|
||||
/* Set bit 0x80 in APBDEV_PMC_CNTRL based on is_blink, do dummy read. */
|
||||
UpdatePmcControlBit(APBDEV_PMC_CNTRL, 0x80, is_blink);
|
||||
|
||||
/* Set bit 0x100000 in APBDEV_PMC_DPD_PADS_ORIDE based on is_blink, do dummy read. */
|
||||
UpdatePmcControlBit(APBDEV_PMC_DPD_PADS_ORIDE, 0x100000, is_blink);
|
||||
}
|
||||
|
||||
void SetWakeEventLevel(u32 index, u32 level) {
|
||||
u32 pmc_wake_level_reg_offset = index <= 0x1F ? APBDEV_PMC_WAKE_LVL : APBDEV_PMC_WAKE2_LVL;
|
||||
u32 pmc_wake_level_mask_reg_offset = index <= 0x1F ? APBDEV_PMC_AUTO_WAKE_LVL_MASK : APBDEV_PMC_AUTO_WAKE2_LVL_MASK;
|
||||
if (level != 2) {
|
||||
std::swap(pmc_wake_level_reg_offset, pmc_wake_level_mask_reg_offset);
|
||||
}
|
||||
|
||||
const u32 mask_val = (1 << (index & 0x1F));
|
||||
|
||||
/* Clear level reg bit. */
|
||||
UpdatePmcControlBit(pmc_wake_level_reg_offset, mask_val, false);
|
||||
|
||||
/* Set or clear mask reg bit. */
|
||||
UpdatePmcControlBit(pmc_wake_level_mask_reg_offset, mask_val, level > 0);
|
||||
}
|
||||
|
||||
void SetWakeEventEnabled(u32 index, bool enabled) {
|
||||
/* Set or clear enabled bit. */
|
||||
UpdatePmcControlBit(index <= 0x1F ? APBDEV_PMC_WAKE_MASK : APBDEV_PMC_WAKE2_MASK, (1 << (index & 0x1F)), enabled);
|
||||
}
|
||||
|
||||
void Boot::SetWakeEventLevel(u32 index, u32 level) {
|
||||
u32 pmc_wake_level_reg_offset = index <= 0x1F ? APBDEV_PMC_WAKE_LVL : APBDEV_PMC_WAKE2_LVL;
|
||||
u32 pmc_wake_level_mask_reg_offset = index <= 0x1F ? APBDEV_PMC_AUTO_WAKE_LVL_MASK : APBDEV_PMC_AUTO_WAKE2_LVL_MASK;
|
||||
if (level != 2) {
|
||||
std::swap(pmc_wake_level_reg_offset, pmc_wake_level_mask_reg_offset);
|
||||
}
|
||||
|
||||
const u32 mask_val = (1 << (index & 0x1F));
|
||||
void SetInitialWakePinConfiguration() {
|
||||
InitializePmcWakeConfiguration(false);
|
||||
|
||||
/* Clear level reg bit. */
|
||||
UpdatePmcControlBit(pmc_wake_level_reg_offset, mask_val, false);
|
||||
/* Set wake event levels, wake event enables. */
|
||||
const WakePinConfig *configs;
|
||||
size_t num_configs;
|
||||
if (spl::GetHardwareType() == spl::HardwareType::Copper) {
|
||||
configs = WakePinConfigsCopper;
|
||||
num_configs = NumWakePinConfigsCopper;
|
||||
} else {
|
||||
configs = WakePinConfigs;
|
||||
num_configs = NumWakePinConfigs;
|
||||
}
|
||||
|
||||
/* Set or clear mask reg bit. */
|
||||
UpdatePmcControlBit(pmc_wake_level_mask_reg_offset, mask_val, level > 0);
|
||||
}
|
||||
|
||||
void Boot::SetWakeEventEnabled(u32 index, bool enabled) {
|
||||
/* Set or clear enabled bit. */
|
||||
UpdatePmcControlBit(index <= 0x1F ? APBDEV_PMC_WAKE_MASK : APBDEV_PMC_WAKE2_MASK, (1 << (index & 0x1F)), enabled);
|
||||
}
|
||||
|
||||
void Boot::SetInitialWakePinConfiguration() {
|
||||
InitializePmcWakeConfiguration(false);
|
||||
|
||||
/* Set wake event levels, wake event enables. */
|
||||
const WakePinConfig *configs;
|
||||
size_t num_configs;
|
||||
if (Boot::GetHardwareType() == HardwareType_Copper) {
|
||||
configs = WakePinConfigsCopper;
|
||||
num_configs = NumWakePinConfigsCopper;
|
||||
} else {
|
||||
configs = WakePinConfigs;
|
||||
num_configs = NumWakePinConfigs;
|
||||
for (size_t i = 0; i < num_configs; i++) {
|
||||
SetWakeEventLevel(configs[i].index, configs[i].level);
|
||||
SetWakeEventEnabled(configs[i].index, configs[i].enabled);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_configs; i++) {
|
||||
Boot::SetWakeEventLevel(configs[i].index, configs[i].level);
|
||||
Boot::SetWakeEventEnabled(configs[i].index, configs[i].enabled);
|
||||
}
|
||||
}
|
||||
|
25
stratosphere/boot/source/boot_wake_pins.hpp
Normal file
25
stratosphere/boot/source/boot_wake_pins.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::boot {
|
||||
|
||||
void SetInitialWakePinConfiguration();
|
||||
|
||||
}
|
102
stratosphere/boot/source/gpio/gpio_initial_configuration.cpp
Normal file
102
stratosphere/boot/source/gpio/gpio_initial_configuration.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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 <stratosphere/spl.hpp>
|
||||
|
||||
#include "gpio_initial_configuration.hpp"
|
||||
#include "gpio_utils.hpp"
|
||||
|
||||
namespace sts::gpio {
|
||||
|
||||
namespace {
|
||||
|
||||
struct InitialConfig {
|
||||
u32 pad_name;
|
||||
GpioDirection direction;
|
||||
GpioValue value;
|
||||
};
|
||||
|
||||
/* Include all initial configuration definitions. */
|
||||
#include "gpio_initial_configuration_icosa.inc"
|
||||
#include "gpio_initial_configuration_copper.inc"
|
||||
#include "gpio_initial_configuration_hoag.inc"
|
||||
#include "gpio_initial_configuration_iowa.inc"
|
||||
|
||||
}
|
||||
|
||||
void SetInitialConfiguration() {
|
||||
const InitialConfig *configs = nullptr;
|
||||
size_t num_configs = 0;
|
||||
const auto hw_type = spl::GetHardwareType();
|
||||
const FirmwareVersion fw_ver = GetRuntimeFirmwareVersion();
|
||||
|
||||
/* Choose GPIO map. */
|
||||
if (fw_ver >= FirmwareVersion_200) {
|
||||
switch (hw_type) {
|
||||
case spl::HardwareType::Icosa:
|
||||
{
|
||||
if (fw_ver >= FirmwareVersion_400) {
|
||||
configs = InitialConfigsIcosa4x;
|
||||
num_configs = NumInitialConfigsIcosa4x;
|
||||
} else {
|
||||
configs = InitialConfigsIcosa;
|
||||
num_configs = NumInitialConfigsIcosa;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case spl::HardwareType::Copper:
|
||||
configs = InitialConfigsCopper;
|
||||
num_configs = NumInitialConfigsCopper;
|
||||
break;
|
||||
case spl::HardwareType::Hoag:
|
||||
configs = InitialConfigsHoag;
|
||||
num_configs = NumInitialConfigsHoag;
|
||||
break;
|
||||
case spl::HardwareType::Iowa:
|
||||
configs = InitialConfigsIowa;
|
||||
num_configs = NumInitialConfigsIowa;
|
||||
break;
|
||||
default:
|
||||
/* Unknown hardware type, we can't proceed. */
|
||||
std::abort();
|
||||
}
|
||||
} else {
|
||||
/* Until 2.0.0, the GPIO map for Icosa was used for all hardware types. */
|
||||
configs = InitialConfigsIcosa;
|
||||
num_configs = NumInitialConfigsIcosa;
|
||||
}
|
||||
|
||||
/* Ensure we found an appropriate config. */
|
||||
if (configs == nullptr) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_configs; i++) {
|
||||
/* Configure the GPIO. */
|
||||
Configure(configs[i].pad_name);
|
||||
|
||||
/* Set the GPIO's direction. */
|
||||
SetDirection(configs[i].pad_name, configs[i].direction);
|
||||
|
||||
if (configs[i].direction == GpioDirection_Output) {
|
||||
/* Set the GPIO's value. */
|
||||
SetValue(configs[i].pad_name, configs[i].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
25
stratosphere/boot/source/gpio/gpio_initial_configuration.hpp
Normal file
25
stratosphere/boot/source/gpio/gpio_initial_configuration.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::gpio {
|
||||
|
||||
void SetInitialConfiguration();
|
||||
|
||||
}
|
@ -14,12 +14,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr GpioInitialConfig GpioInitialConfigsCopper[] = {
|
||||
constexpr InitialConfig InitialConfigsCopper[] = {
|
||||
{0x40, GpioDirection_Output, GpioValue_Low},
|
||||
{0x05, GpioDirection_Output, GpioValue_Low},
|
||||
{0x41, GpioDirection_Input, GpioValue_High},
|
||||
@ -66,4 +61,4 @@ static constexpr GpioInitialConfig GpioInitialConfigsCopper[] = {
|
||||
{0x4E, GpioDirection_Input, GpioValue_Low},
|
||||
};
|
||||
|
||||
static constexpr u32 GpioNumInitialConfigsCopper = (sizeof(GpioInitialConfigsCopper) / sizeof(GpioInitialConfigsCopper[0]));
|
||||
constexpr u32 NumInitialConfigsCopper = (sizeof(InitialConfigsCopper) / sizeof(InitialConfigsCopper[0]));
|
@ -14,12 +14,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr GpioInitialConfig GpioInitialConfigsIowa[] = {
|
||||
constexpr InitialConfig InitialConfigsHoag[] = {
|
||||
{0x04, GpioDirection_Input, GpioValue_High},
|
||||
{0x05, GpioDirection_Output, GpioValue_Low},
|
||||
{0x06, GpioDirection_Input, GpioValue_Low},
|
||||
@ -78,7 +73,6 @@ static constexpr GpioInitialConfig GpioInitialConfigsIowa[] = {
|
||||
{0x35, GpioDirection_Input, GpioValue_High},
|
||||
{0x2C, GpioDirection_Output, GpioValue_Low},
|
||||
{0x36, GpioDirection_Output, GpioValue_Low},
|
||||
|
||||
};
|
||||
|
||||
static constexpr u32 GpioNumInitialConfigsIowa = (sizeof(GpioInitialConfigsIowa) / sizeof(GpioInitialConfigsIowa[0]));
|
||||
constexpr u32 NumInitialConfigsHoag = (sizeof(InitialConfigsHoag) / sizeof(InitialConfigsHoag[0]));
|
@ -14,12 +14,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr GpioInitialConfig GpioInitialConfigsIcosa[] = {
|
||||
constexpr InitialConfig InitialConfigsIcosa[] = {
|
||||
{0x04, GpioDirection_Input, GpioValue_High},
|
||||
{0x05, GpioDirection_Output, GpioValue_Low},
|
||||
{0x06, GpioDirection_Input, GpioValue_Low},
|
||||
@ -82,9 +77,9 @@ static constexpr GpioInitialConfig GpioInitialConfigsIcosa[] = {
|
||||
{0x36, GpioDirection_Output, GpioValue_Low},
|
||||
};
|
||||
|
||||
static constexpr u32 GpioNumInitialConfigsIcosa = (sizeof(GpioInitialConfigsIcosa) / sizeof(GpioInitialConfigsIcosa[0]));
|
||||
constexpr u32 NumInitialConfigsIcosa = (sizeof(InitialConfigsIcosa) / sizeof(InitialConfigsIcosa[0]));
|
||||
|
||||
static constexpr GpioInitialConfig GpioInitialConfigsIcosa4x[] = {
|
||||
constexpr InitialConfig InitialConfigsIcosa4x[] = {
|
||||
{0x04, GpioDirection_Input, GpioValue_High},
|
||||
{0x05, GpioDirection_Output, GpioValue_Low},
|
||||
{0x06, GpioDirection_Input, GpioValue_Low},
|
||||
@ -147,4 +142,4 @@ static constexpr GpioInitialConfig GpioInitialConfigsIcosa4x[] = {
|
||||
{0x36, GpioDirection_Output, GpioValue_Low},
|
||||
};
|
||||
|
||||
static constexpr u32 GpioNumInitialConfigsIcosa4x = (sizeof(GpioInitialConfigsIcosa4x) / sizeof(GpioInitialConfigsIcosa4x[0]));
|
||||
constexpr u32 NumInitialConfigsIcosa4x = (sizeof(InitialConfigsIcosa4x) / sizeof(InitialConfigsIcosa4x[0]));
|
@ -14,12 +14,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#include "boot_types.hpp"
|
||||
|
||||
static constexpr GpioInitialConfig GpioInitialConfigsHoag[] = {
|
||||
constexpr InitialConfig InitialConfigsIowa[] = {
|
||||
{0x04, GpioDirection_Input, GpioValue_High},
|
||||
{0x05, GpioDirection_Output, GpioValue_Low},
|
||||
{0x06, GpioDirection_Input, GpioValue_Low},
|
||||
@ -78,7 +73,6 @@ static constexpr GpioInitialConfig GpioInitialConfigsHoag[] = {
|
||||
{0x35, GpioDirection_Input, GpioValue_High},
|
||||
{0x2C, GpioDirection_Output, GpioValue_Low},
|
||||
{0x36, GpioDirection_Output, GpioValue_Low},
|
||||
|
||||
};
|
||||
|
||||
static constexpr u32 GpioNumInitialConfigsHoag = (sizeof(GpioInitialConfigsHoag) / sizeof(GpioInitialConfigsHoag[0]));
|
||||
constexpr u32 NumInitialConfigsIowa = (sizeof(InitialConfigsIowa) / sizeof(InitialConfigsIowa[0]));
|
@ -14,13 +14,10 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
constexpr u32 InvalidPadName = UINT32_MAX;
|
||||
|
||||
static constexpr u32 GpioInvalid = UINT32_MAX;
|
||||
|
||||
static constexpr u32 GpioMap[] = {
|
||||
GpioInvalid, /* Invalid */
|
||||
constexpr u32 Map[] = {
|
||||
InvalidPadName, /* Invalid */
|
||||
0x000000CC, /* Port Z, Pin 4 */
|
||||
0x00000024, /* Port E, Pin 4 */
|
||||
0x0000003C, /* Port H, Pin 4 */
|
||||
@ -83,7 +80,7 @@ static constexpr u32 GpioMap[] = {
|
||||
0x00000026, /* Port E, Pin 6 */
|
||||
|
||||
/* Copper only */
|
||||
GpioInvalid, /* Invalid */
|
||||
InvalidPadName, /* Invalid */
|
||||
0x00000033, /* Port G, Pin 3 */
|
||||
0x0000001C, /* Port D, Pin 4 */
|
||||
0x000000D9, /* Port BB, Pin 1 */
|
||||
@ -108,4 +105,4 @@ static constexpr u32 GpioMap[] = {
|
||||
0x00000056, /* Port K, Pin 6 */
|
||||
};
|
||||
|
||||
static constexpr u32 GpioPadNameMax = (sizeof(GpioMap) / sizeof(GpioMap[0]));
|
||||
static constexpr u32 PadNameMax = (sizeof(Map) / sizeof(Map[0]));
|
132
stratosphere/boot/source/gpio/gpio_utils.cpp
Normal file
132
stratosphere/boot/source/gpio/gpio_utils.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 <stratosphere/reg.hpp>
|
||||
|
||||
#include "gpio_utils.hpp"
|
||||
|
||||
namespace sts::gpio {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Pull in GPIO map definitions. */
|
||||
#include "gpio_map.inc"
|
||||
|
||||
constexpr u32 PhysicalBase = 0x6000D000;
|
||||
|
||||
/* Globals. */
|
||||
bool g_initialized_gpio_vaddr = false;
|
||||
uintptr_t g_gpio_vaddr = 0;
|
||||
|
||||
/* Helpers. */
|
||||
inline u32 GetPadDescriptor(u32 gpio_pad_name) {
|
||||
if (gpio_pad_name >= PadNameMax) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return Map[gpio_pad_name];
|
||||
}
|
||||
|
||||
uintptr_t GetBaseAddress() {
|
||||
if (!g_initialized_gpio_vaddr) {
|
||||
g_gpio_vaddr = GetIoMapping(PhysicalBase, 0x1000);
|
||||
g_initialized_gpio_vaddr = true;
|
||||
}
|
||||
return g_gpio_vaddr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
u32 Configure(u32 gpio_pad_name) {
|
||||
uintptr_t gpio_base_vaddr = GetBaseAddress();
|
||||
|
||||
/* Fetch this GPIO's pad descriptor */
|
||||
const u32 gpio_pad_desc = GetPadDescriptor(gpio_pad_name);
|
||||
|
||||
/* Discard invalid GPIOs */
|
||||
if (gpio_pad_desc == InvalidPadName) {
|
||||
return InvalidPadName;
|
||||
}
|
||||
|
||||
/* Convert the GPIO pad descriptor into its register offset */
|
||||
u32 gpio_reg_offset = (((gpio_pad_desc << 0x03) & 0xFFFFFF00) | ((gpio_pad_desc >> 0x01) & 0x0C));
|
||||
|
||||
/* Extract the bit and lock values from the GPIO pad descriptor */
|
||||
u32 gpio_cnf_val = ((0x01 << ((gpio_pad_desc & 0x07) | 0x08)) | (0x01 << (gpio_pad_desc & 0x07)));
|
||||
|
||||
/* Write to the appropriate GPIO_CNF_x register (upper offset) */
|
||||
reg::Write(gpio_base_vaddr + gpio_reg_offset + 0x80, gpio_cnf_val);
|
||||
|
||||
/* Do a dummy read from GPIO_CNF_x register (lower offset) */
|
||||
gpio_cnf_val = reg::Read(gpio_base_vaddr + gpio_reg_offset + 0x00);
|
||||
|
||||
return gpio_cnf_val;
|
||||
}
|
||||
|
||||
u32 SetDirection(u32 gpio_pad_name, GpioDirection dir) {
|
||||
uintptr_t gpio_base_vaddr = GetBaseAddress();
|
||||
|
||||
/* Fetch this GPIO's pad descriptor */
|
||||
const u32 gpio_pad_desc = GetPadDescriptor(gpio_pad_name);
|
||||
|
||||
/* Discard invalid GPIOs */
|
||||
if (gpio_pad_desc == InvalidPadName) {
|
||||
return InvalidPadName;
|
||||
}
|
||||
|
||||
/* Convert the GPIO pad descriptor into its register offset */
|
||||
u32 gpio_reg_offset = (((gpio_pad_desc << 0x03) & 0xFFFFFF00) | ((gpio_pad_desc >> 0x01) & 0x0C));
|
||||
|
||||
/* Set the direction bit and lock values */
|
||||
u32 gpio_oe_val = ((0x01 << ((gpio_pad_desc & 0x07) | 0x08)) | (static_cast<u32>(dir) << (gpio_pad_desc & 0x07)));
|
||||
|
||||
/* Write to the appropriate GPIO_OE_x register (upper offset) */
|
||||
reg::Write(gpio_base_vaddr + gpio_reg_offset + 0x90, gpio_oe_val);
|
||||
|
||||
/* Do a dummy read from GPIO_OE_x register (lower offset) */
|
||||
gpio_oe_val = reg::Read(gpio_base_vaddr + gpio_reg_offset + 0x10);
|
||||
|
||||
return gpio_oe_val;
|
||||
}
|
||||
|
||||
u32 SetValue(u32 gpio_pad_name, GpioValue val) {
|
||||
uintptr_t gpio_base_vaddr = GetBaseAddress();
|
||||
|
||||
/* Fetch this GPIO's pad descriptor */
|
||||
const u32 gpio_pad_desc = GetPadDescriptor(gpio_pad_name);
|
||||
|
||||
/* Discard invalid GPIOs */
|
||||
if (gpio_pad_desc == InvalidPadName) {
|
||||
return InvalidPadName;
|
||||
}
|
||||
|
||||
/* Convert the GPIO pad descriptor into its register offset */
|
||||
u32 gpio_reg_offset = (((gpio_pad_desc << 0x03) & 0xFFFFFF00) | ((gpio_pad_desc >> 0x01) & 0x0C));
|
||||
|
||||
/* Set the output bit and lock values */
|
||||
u32 gpio_out_val = ((0x01 << ((gpio_pad_desc & 0x07) | 0x08)) | (static_cast<u32>(val) << (gpio_pad_desc & 0x07)));
|
||||
|
||||
/* Write to the appropriate GPIO_OUT_x register (upper offset) */
|
||||
reg::Write(gpio_base_vaddr + gpio_reg_offset + 0xA0, gpio_out_val);
|
||||
|
||||
/* Do a dummy read from GPIO_OUT_x register (lower offset) */
|
||||
gpio_out_val = reg::Read(gpio_base_vaddr + gpio_reg_offset + 0x20);
|
||||
|
||||
return gpio_out_val;
|
||||
}
|
||||
|
||||
}
|
28
stratosphere/boot/source/gpio/gpio_utils.hpp
Normal file
28
stratosphere/boot/source/gpio/gpio_utils.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::gpio {
|
||||
|
||||
/* GPIO Utilities. */
|
||||
u32 Configure(u32 gpio_pad_name);
|
||||
u32 SetDirection(u32 gpio_pad_name, GpioDirection dir);
|
||||
u32 SetValue(u32 gpio_pad_name, GpioValue val);
|
||||
|
||||
}
|
188
stratosphere/boot/source/i2c/driver/i2c_api.cpp
Normal file
188
stratosphere/boot/source/i2c/driver/i2c_api.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_api.hpp"
|
||||
#include "impl/i2c_resource_manager.hpp"
|
||||
|
||||
namespace sts::i2c::driver {
|
||||
|
||||
namespace {
|
||||
|
||||
/* For convenience. */
|
||||
using CommandHandler = Result (*)(const u8 **cur_cmd, u8 **cur_dst, Session& session);
|
||||
|
||||
/* Command handlers. */
|
||||
Result SendHandler(const u8 **cur_cmd, u8 **cur_dst, Session& session) {
|
||||
I2cTransactionOption option = static_cast<I2cTransactionOption>(
|
||||
(((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0)
|
||||
);
|
||||
(*cur_cmd)++;
|
||||
|
||||
size_t num_bytes = (**cur_cmd);
|
||||
(*cur_cmd)++;
|
||||
|
||||
R_TRY(Send(session, *cur_cmd, num_bytes, option));
|
||||
(*cur_cmd) += num_bytes;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ReceiveHandler(const u8 **cur_cmd, u8 **cur_dst, Session& session) {
|
||||
I2cTransactionOption option = static_cast<I2cTransactionOption>(
|
||||
(((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0)
|
||||
);
|
||||
(*cur_cmd)++;
|
||||
|
||||
size_t num_bytes = (**cur_cmd);
|
||||
(*cur_cmd)++;
|
||||
|
||||
R_TRY(Receive(session, *cur_dst, num_bytes, option));
|
||||
(*cur_dst) += num_bytes;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SubCommandHandler(const u8 **cur_cmd, u8 **cur_dst, Session& session) {
|
||||
const SubCommand sub_cmd = static_cast<SubCommand>((**cur_cmd) >> 2);
|
||||
(*cur_cmd)++;
|
||||
|
||||
switch (sub_cmd) {
|
||||
case SubCommand::Sleep:
|
||||
{
|
||||
const size_t us = (**cur_cmd);
|
||||
(*cur_cmd)++;
|
||||
svcSleepThread(us * 1'000ul);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Command handler list. */
|
||||
constexpr CommandHandler g_cmd_handlers[static_cast<size_t>(Command::Count)] = {
|
||||
SendHandler,
|
||||
ReceiveHandler,
|
||||
SubCommandHandler,
|
||||
};
|
||||
|
||||
inline impl::ResourceManager &GetResourceManager() {
|
||||
return impl::ResourceManager::GetInstance();
|
||||
}
|
||||
|
||||
inline void CheckInitialized() {
|
||||
if (!GetResourceManager().IsInitialized()) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Initialization. */
|
||||
void Initialize() {
|
||||
GetResourceManager().Initialize();
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
GetResourceManager().Finalize();
|
||||
}
|
||||
|
||||
/* Session management. */
|
||||
void OpenSession(Session *out_session, I2cDevice device) {
|
||||
CheckInitialized();
|
||||
if (!impl::IsDeviceSupported(device)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
const auto bus = impl::GetDeviceBus(device);
|
||||
const auto slave_address = impl::GetDeviceSlaveAddress(device);
|
||||
const auto addressing_mode = impl::GetDeviceAddressingMode(device);
|
||||
const auto speed_mode = impl::GetDeviceSpeedMode(device);
|
||||
const auto max_retries = impl::GetDeviceMaxRetries(device);
|
||||
const auto retry_wait_time = impl::GetDeviceRetryWaitTime(device);
|
||||
GetResourceManager().OpenSession(out_session, bus, slave_address, addressing_mode, speed_mode, max_retries, retry_wait_time);
|
||||
}
|
||||
|
||||
void CloseSession(Session &session) {
|
||||
CheckInitialized();
|
||||
GetResourceManager().CloseSession(session);
|
||||
}
|
||||
|
||||
/* Communication. */
|
||||
Result Send(Session &session, const void *src, size_t size, I2cTransactionOption option) {
|
||||
CheckInitialized();
|
||||
if (src == nullptr || size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::scoped_lock<HosMutex &> lk(GetResourceManager().GetTransactionMutex(impl::ConvertFromIndex(session.bus_idx)));
|
||||
return GetResourceManager().GetSession(session.session_id).DoTransactionWithRetry(nullptr, src, size, option, impl::Command::Send);
|
||||
}
|
||||
|
||||
Result Receive(Session &session, void *dst, size_t size, I2cTransactionOption option) {
|
||||
CheckInitialized();
|
||||
if (dst == nullptr || size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::scoped_lock<HosMutex &> lk(GetResourceManager().GetTransactionMutex(impl::ConvertFromIndex(session.bus_idx)));
|
||||
return GetResourceManager().GetSession(session.session_id).DoTransactionWithRetry(dst, nullptr, size, option, impl::Command::Receive);
|
||||
}
|
||||
|
||||
Result ExecuteCommandList(Session &session, void *dst, size_t size, const void *cmd_list, size_t cmd_list_size) {
|
||||
CheckInitialized();
|
||||
if (dst == nullptr || size == 0 || cmd_list == nullptr || cmd_list_size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u8 *cur_dst = static_cast<u8 *>(dst);
|
||||
const u8 *cur_cmd = static_cast<const u8 *>(cmd_list);
|
||||
const u8 *cmd_list_end = cur_cmd + cmd_list_size;
|
||||
|
||||
while (cur_cmd < cmd_list_end) {
|
||||
Command cmd = static_cast<Command>((*cur_cmd) & 3);
|
||||
if (cmd >= Command::Count) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
R_TRY(g_cmd_handlers[static_cast<size_t>(cmd)](&cur_cmd, &cur_dst, session));
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Power management. */
|
||||
void SuspendBuses() {
|
||||
GetResourceManager().SuspendBuses();
|
||||
}
|
||||
|
||||
void ResumeBuses() {
|
||||
GetResourceManager().ResumeBuses();
|
||||
}
|
||||
|
||||
void SuspendPowerBus() {
|
||||
GetResourceManager().SuspendPowerBus();
|
||||
}
|
||||
|
||||
void ResumePowerBus() {
|
||||
GetResourceManager().ResumePowerBus();
|
||||
}
|
||||
|
||||
}
|
50
stratosphere/boot/source/i2c/driver/i2c_api.hpp
Normal file
50
stratosphere/boot/source/i2c/driver/i2c_api.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../i2c_types.hpp"
|
||||
#include "../i2c_command_list.hpp"
|
||||
|
||||
namespace sts::i2c::driver {
|
||||
|
||||
struct Session {
|
||||
size_t bus_idx;
|
||||
size_t session_id;
|
||||
};
|
||||
|
||||
/* Initialization. */
|
||||
void Initialize();
|
||||
void Finalize();
|
||||
|
||||
/* Session management. */
|
||||
void OpenSession(Session *out_session, I2cDevice device);
|
||||
void CloseSession(Session &session);
|
||||
|
||||
/* Communication. */
|
||||
Result Send(Session &session, const void *src, size_t size, I2cTransactionOption option);
|
||||
Result Receive(Session &session, void *dst, size_t size, I2cTransactionOption option);
|
||||
Result ExecuteCommandList(Session &session, void *dst, size_t size, const void *cmd_list, size_t cmd_list_size);
|
||||
|
||||
/* Power management. */
|
||||
void SuspendBuses();
|
||||
void ResumeBuses();
|
||||
void SuspendPowerBus();
|
||||
void ResumePowerBus();
|
||||
|
||||
}
|
478
stratosphere/boot/source/i2c/driver/impl/i2c_bus_accessor.cpp
Normal file
478
stratosphere/boot/source/i2c/driver/impl/i2c_bus_accessor.cpp
Normal file
@ -0,0 +1,478 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_pcv.hpp"
|
||||
#include "i2c_bus_accessor.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
void BusAccessor::Open(Bus bus, SpeedMode speed_mode) {
|
||||
std::scoped_lock<HosMutex> lk(this->open_mutex);
|
||||
/* Open new session. */
|
||||
this->open_sessions++;
|
||||
|
||||
/* Ensure we're good if this isn't our first session. */
|
||||
if (this->open_sessions > 1) {
|
||||
if (this->speed_mode != speed_mode) {
|
||||
std::abort();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set all members for chosen bus. */
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(this->register_mutex);
|
||||
/* Set bus/registers. */
|
||||
this->SetBus(bus);
|
||||
/* Set pcv module. */
|
||||
this->pcv_module = ConvertToPcvModule(bus);
|
||||
/* Set speed mode. */
|
||||
this->speed_mode = speed_mode;
|
||||
/* Setup interrupt event. */
|
||||
this->CreateInterruptEvent(bus);
|
||||
}
|
||||
}
|
||||
|
||||
void BusAccessor::Close() {
|
||||
std::scoped_lock<HosMutex> lk(this->open_mutex);
|
||||
/* Close current session. */
|
||||
this->open_sessions--;
|
||||
if (this->open_sessions > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Close interrupt event. */
|
||||
eventClose(&this->interrupt_event);
|
||||
|
||||
/* Close PCV. */
|
||||
pcv::Finalize();
|
||||
|
||||
this->suspended = false;
|
||||
}
|
||||
|
||||
void BusAccessor::Suspend() {
|
||||
std::scoped_lock<HosMutex> lk(this->open_mutex);
|
||||
std::scoped_lock<HosMutex> lk_reg(this->register_mutex);
|
||||
|
||||
if (!this->suspended) {
|
||||
this->suspended = true;
|
||||
|
||||
if (this->pcv_module != PcvModule_I2C5) {
|
||||
this->DisableClock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BusAccessor::Resume() {
|
||||
if (this->suspended) {
|
||||
this->DoInitialConfig();
|
||||
this->suspended = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BusAccessor::DoInitialConfig() {
|
||||
std::scoped_lock<HosMutex> lk(this->register_mutex);
|
||||
|
||||
if (this->pcv_module != PcvModule_I2C5) {
|
||||
pcv::Initialize();
|
||||
}
|
||||
|
||||
this->ResetController();
|
||||
this->SetClock(this->speed_mode);
|
||||
this->SetPacketMode();
|
||||
this->FlushFifos();
|
||||
}
|
||||
|
||||
size_t BusAccessor::GetOpenSessions() const {
|
||||
return this->open_sessions;
|
||||
}
|
||||
|
||||
bool BusAccessor::GetBusy() const {
|
||||
/* Nintendo has a loop here that calls a member function to check if busy, retrying a few times. */
|
||||
/* This member function does "return false". */
|
||||
/* We will not bother with the loop. */
|
||||
return false;
|
||||
}
|
||||
|
||||
void BusAccessor::OnStartTransaction() const {
|
||||
/* Nothing actually happens here. */
|
||||
}
|
||||
|
||||
void BusAccessor::OnStopTransaction() const {
|
||||
/* Nothing actually happens here. */
|
||||
}
|
||||
|
||||
Result BusAccessor::StartTransaction(Command command, AddressingMode addressing_mode, u32 slave_address) {
|
||||
/* Nothing actually happens here... */
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BusAccessor::Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
|
||||
std::scoped_lock<HosMutex> lk(this->register_mutex);
|
||||
const u8 *cur_src = data;
|
||||
size_t remaining = num_bytes;
|
||||
|
||||
/* Set interrupt enable, clear interrupt status. */
|
||||
reg::Write(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8E);
|
||||
reg::Write(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0, 0xFC);
|
||||
|
||||
ON_SCOPE_EXIT { this->ClearInterruptMask(); };
|
||||
|
||||
/* Send header. */
|
||||
this->WriteTransferHeader(TransferMode::Send, option, addressing_mode, slave_address, num_bytes);
|
||||
|
||||
/* Send bytes. */
|
||||
while (true) {
|
||||
const u32 fifo_status = reg::Read(&this->i2c_registers->I2C_FIFO_STATUS_0);
|
||||
const size_t fifo_cnt = (fifo_status >> 4);
|
||||
|
||||
for (size_t fifo_idx = 0; remaining > 0 && fifo_idx < fifo_cnt; fifo_idx++) {
|
||||
const size_t cur_bytes = std::min(remaining, sizeof(u32));
|
||||
u32 val = 0;
|
||||
for (size_t i = 0; i < cur_bytes; i++) {
|
||||
val |= cur_src[i] << (8 * i);
|
||||
}
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, val);
|
||||
|
||||
cur_src += cur_bytes;
|
||||
remaining -= cur_bytes;
|
||||
}
|
||||
|
||||
if (remaining == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
eventClear(&this->interrupt_event);
|
||||
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
|
||||
this->HandleTransactionResult(ResultI2cBusBusy);
|
||||
eventClear(&this->interrupt_event);
|
||||
return ResultI2cTimedOut;
|
||||
}
|
||||
|
||||
R_TRY(this->GetAndHandleTransactionResult());
|
||||
}
|
||||
|
||||
reg::Write(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8C);
|
||||
|
||||
/* Wait for successful completion. */
|
||||
while (true) {
|
||||
R_TRY(this->GetAndHandleTransactionResult());
|
||||
|
||||
/* Check PACKET_XFER_COMPLETE */
|
||||
const u32 interrupt_status = reg::Read(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
|
||||
if (interrupt_status & 0x80) {
|
||||
R_TRY(this->GetAndHandleTransactionResult());
|
||||
break;
|
||||
}
|
||||
|
||||
eventClear(&this->interrupt_event);
|
||||
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
|
||||
this->HandleTransactionResult(ResultI2cBusBusy);
|
||||
eventClear(&this->interrupt_event);
|
||||
return ResultI2cTimedOut;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result BusAccessor::Receive(u8 *out_data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
|
||||
std::scoped_lock<HosMutex> lk(this->register_mutex);
|
||||
u8 *cur_dst = out_data;
|
||||
size_t remaining = num_bytes;
|
||||
|
||||
/* Set interrupt enable, clear interrupt status. */
|
||||
reg::Write(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8D);
|
||||
reg::Write(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0, 0xFC);
|
||||
|
||||
/* Send header. */
|
||||
this->WriteTransferHeader(TransferMode::Receive, option, addressing_mode, slave_address, num_bytes);
|
||||
|
||||
/* Receive bytes. */
|
||||
while (remaining > 0) {
|
||||
eventClear(&this->interrupt_event);
|
||||
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
|
||||
this->HandleTransactionResult(ResultI2cBusBusy);
|
||||
this->ClearInterruptMask();
|
||||
eventClear(&this->interrupt_event);
|
||||
return ResultI2cTimedOut;
|
||||
}
|
||||
|
||||
R_TRY(this->GetAndHandleTransactionResult());
|
||||
|
||||
const u32 fifo_status = reg::Read(&this->i2c_registers->I2C_FIFO_STATUS_0);
|
||||
const size_t fifo_cnt = std::min((remaining + 3) >> 2, static_cast<size_t>(fifo_status & 0xF));
|
||||
|
||||
for (size_t fifo_idx = 0; remaining > 0 && fifo_idx < fifo_cnt; fifo_idx++) {
|
||||
const u32 val = reg::Read(&this->i2c_registers->I2C_I2C_RX_FIFO_0);
|
||||
const size_t cur_bytes = std::min(remaining, sizeof(u32));
|
||||
for (size_t i = 0; i < cur_bytes; i++) {
|
||||
cur_dst[i] = static_cast<u8>((val >> (8 * i)) & 0xFF);
|
||||
}
|
||||
|
||||
cur_dst += cur_bytes;
|
||||
remaining -= cur_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/* N doesn't do ClearInterruptMask. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void BusAccessor::SetBus(Bus bus) {
|
||||
this->bus = bus;
|
||||
this->i2c_registers = GetRegisters(bus);
|
||||
this->clkrst_registers.SetBus(bus);
|
||||
}
|
||||
|
||||
void BusAccessor::CreateInterruptEvent(Bus bus) {
|
||||
static constexpr u64 s_interrupts[] = {
|
||||
0x46, 0x74, 0x7C, 0x98, 0x55, 0x5F
|
||||
};
|
||||
if (ConvertToIndex(bus) >= sizeof(s_interrupts) / sizeof(s_interrupts[0])) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Handle evt_h;
|
||||
if (R_FAILED(svcCreateInterruptEvent(&evt_h, s_interrupts[ConvertToIndex(bus)], 1))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
eventLoadRemote(&this->interrupt_event, evt_h, false);
|
||||
}
|
||||
|
||||
void BusAccessor::SetClock(SpeedMode speed_mode) {
|
||||
u32 t_high, t_low;
|
||||
u32 clk_div, src_div;
|
||||
u32 debounce;
|
||||
|
||||
switch (speed_mode) {
|
||||
case SpeedMode::Normal:
|
||||
t_high = 2;
|
||||
t_low = 4;
|
||||
clk_div = 0x19;
|
||||
src_div = 0x13;
|
||||
debounce = 2;
|
||||
break;
|
||||
case SpeedMode::Fast:
|
||||
t_high = 2;
|
||||
t_low = 4;
|
||||
clk_div = 0x19;
|
||||
src_div = 0x04;
|
||||
debounce = 2;
|
||||
break;
|
||||
case SpeedMode::FastPlus:
|
||||
t_high = 2;
|
||||
t_low = 4;
|
||||
clk_div = 0x10;
|
||||
src_div = 0x02;
|
||||
debounce = 0;
|
||||
break;
|
||||
case SpeedMode::HighSpeed:
|
||||
t_high = 3;
|
||||
t_low = 8;
|
||||
clk_div = 0x02;
|
||||
src_div = 0x02;
|
||||
debounce = 0;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (speed_mode == SpeedMode::HighSpeed) {
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_HS_INTERFACE_TIMING_0_0, (t_high << 8) | (t_low));
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_CLK_DIVISOR_REGISTER_0, clk_div);
|
||||
} else {
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_INTERFACE_TIMING_0_0, (t_high << 8) | (t_low));
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_CLK_DIVISOR_REGISTER_0, (clk_div << 16));
|
||||
}
|
||||
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_CNFG_0, debounce);
|
||||
reg::Read(&this->i2c_registers->I2C_I2C_CNFG_0);
|
||||
|
||||
if (this->pcv_module != PcvModule_I2C5) {
|
||||
if (R_FAILED(pcv::SetReset(this->pcv_module, true))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pcv::SetClockRate(this->pcv_module, (408'000'000) / (src_div + 1)))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pcv::SetReset(this->pcv_module, false))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BusAccessor::ResetController() const {
|
||||
if (this->pcv_module != PcvModule_I2C5) {
|
||||
if (R_FAILED(pcv::SetReset(this->pcv_module, true))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pcv::SetClockRate(this->pcv_module, 81'600'000))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pcv::SetReset(this->pcv_module, false))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BusAccessor::ClearBus() const {
|
||||
bool success = false;
|
||||
for (size_t i = 0; i < 3 && !success; i++) {
|
||||
success = true;
|
||||
|
||||
this->ResetController();
|
||||
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x90000);
|
||||
reg::SetBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x4);
|
||||
reg::SetBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x2);
|
||||
|
||||
reg::SetBits(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0, 0x1);
|
||||
{
|
||||
u64 start_tick = armGetSystemTick();
|
||||
while (reg::Read(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0) & 1) {
|
||||
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
reg::SetBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x1);
|
||||
{
|
||||
u64 start_tick = armGetSystemTick();
|
||||
while (reg::Read(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0) & 1) {
|
||||
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
u64 start_tick = armGetSystemTick();
|
||||
while (reg::Read(&this->i2c_registers->I2C_I2C_BUS_CLEAR_STATUS_0) & 1) {
|
||||
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BusAccessor::DisableClock() {
|
||||
if (R_FAILED(pcv::SetClockEnabled(this->pcv_module, false))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void BusAccessor::SetPacketMode() {
|
||||
/* Set PACKET_MODE_EN, MSTR_CONFIG_LOAD */
|
||||
reg::SetBits(&this->i2c_registers->I2C_I2C_CNFG_0, 0x400);
|
||||
reg::SetBits(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0, 0x1);
|
||||
|
||||
/* Set TX_FIFO_TRIGGER, RX_FIFO_TRIGGER */
|
||||
reg::Write(&this->i2c_registers->I2C_FIFO_CONTROL_0, 0xFC);
|
||||
}
|
||||
|
||||
Result BusAccessor::FlushFifos() {
|
||||
reg::Write(&this->i2c_registers->I2C_FIFO_CONTROL_0, 0xFF);
|
||||
|
||||
/* Wait for flush to finish, check every ms for 5 ms. */
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
if (!(reg::Read(&this->i2c_registers->I2C_FIFO_CONTROL_0) & 3)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
svcSleepThread(1'000'000ul);
|
||||
}
|
||||
|
||||
return ResultI2cBusBusy;
|
||||
}
|
||||
|
||||
Result BusAccessor::GetTransactionResult() const {
|
||||
const u32 packet_status = reg::Read(&this->i2c_registers->I2C_PACKET_TRANSFER_STATUS_0);
|
||||
const u32 interrupt_status = reg::Read(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
|
||||
|
||||
/* Check for no ack. */
|
||||
if ((packet_status & 0xC) || (interrupt_status & 0x8)) {
|
||||
return ResultI2cNoAck;
|
||||
}
|
||||
|
||||
/* Check for arb lost. */
|
||||
if ((packet_status & 0x2) || (interrupt_status & 0x4)) {
|
||||
this->ClearBus();
|
||||
return ResultI2cBusBusy;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void BusAccessor::HandleTransactionResult(Result result) {
|
||||
if (R_FAILED(result)) {
|
||||
if (result == ResultI2cNoAck || result == ResultI2cBusBusy) {
|
||||
this->ResetController();
|
||||
this->SetClock(this->speed_mode);
|
||||
this->SetPacketMode();
|
||||
this->FlushFifos();
|
||||
} else {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result BusAccessor::GetAndHandleTransactionResult() {
|
||||
const Result transaction_res = this->GetTransactionResult();
|
||||
R_TRY_CLEANUP(transaction_res, {
|
||||
this->HandleTransactionResult(transaction_res);
|
||||
this->ClearInterruptMask();
|
||||
eventClear(&this->interrupt_event);
|
||||
});
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void BusAccessor::WriteTransferHeader(TransferMode transfer_mode, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address, size_t num_bytes) {
|
||||
this->FlushFifos();
|
||||
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, 0x10);
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, static_cast<u32>(num_bytes - 1) & 0xFFF);
|
||||
|
||||
const u32 slave_addr_val = ((transfer_mode == TransferMode::Receive) & 1) | ((slave_address & 0x7F) << 1);
|
||||
u32 hdr_val = 0;
|
||||
hdr_val |= ((this->speed_mode == SpeedMode::HighSpeed) & 1) << 22;
|
||||
hdr_val |= ((transfer_mode == TransferMode::Receive) & 1) << 19;
|
||||
hdr_val |= ((addressing_mode != AddressingMode::SevenBit) & 1) << 18;
|
||||
hdr_val |= (1 << 17);
|
||||
hdr_val |= (((option & I2cTransactionOption_Stop) == 0) & 1) << 16;
|
||||
hdr_val |= slave_addr_val;
|
||||
|
||||
reg::Write(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, hdr_val);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_driver_types.hpp"
|
||||
#include "i2c_registers.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
class BusAccessor {
|
||||
private:
|
||||
enum class TransferMode {
|
||||
Send = 0,
|
||||
Receive = 1,
|
||||
};
|
||||
static constexpr u64 InterruptTimeout = 100'000'000ul;
|
||||
private:
|
||||
Event interrupt_event;
|
||||
HosMutex open_mutex;
|
||||
HosMutex register_mutex;
|
||||
Registers *i2c_registers = nullptr;
|
||||
ClkRstRegisters clkrst_registers;
|
||||
SpeedMode speed_mode = SpeedMode::Fast;
|
||||
size_t open_sessions = 0;
|
||||
Bus bus = Bus::I2C1;
|
||||
PcvModule pcv_module = PcvModule_I2C1;
|
||||
bool suspended = false;
|
||||
public:
|
||||
BusAccessor() { /* ... */ }
|
||||
private:
|
||||
inline void ClearInterruptMask() const {
|
||||
reg::Write(&i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0);
|
||||
reg::Read(&i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0);
|
||||
}
|
||||
|
||||
void SetBus(Bus bus);
|
||||
void CreateInterruptEvent(Bus bus);
|
||||
void SetClock(SpeedMode speed_mode);
|
||||
|
||||
void ResetController() const;
|
||||
void ClearBus() const;
|
||||
void DisableClock();
|
||||
void SetPacketMode();
|
||||
Result FlushFifos();
|
||||
|
||||
Result GetTransactionResult() const;
|
||||
void HandleTransactionResult(Result result);
|
||||
Result GetAndHandleTransactionResult();
|
||||
|
||||
void WriteTransferHeader(TransferMode transfer_mode, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address, size_t num_bytes);
|
||||
public:
|
||||
void Open(Bus bus, SpeedMode speed_mode);
|
||||
void Close();
|
||||
void Suspend();
|
||||
void Resume();
|
||||
void DoInitialConfig();
|
||||
|
||||
size_t GetOpenSessions() const;
|
||||
bool GetBusy() const;
|
||||
|
||||
void OnStartTransaction() const;
|
||||
Result StartTransaction(Command command, AddressingMode addressing_mode, u32 slave_address);
|
||||
Result Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address);
|
||||
Result Receive(u8 *out_data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address);
|
||||
void OnStopTransaction() const;
|
||||
};
|
||||
|
||||
|
||||
}
|
125
stratosphere/boot/source/i2c/driver/impl/i2c_device_config.cpp
Normal file
125
stratosphere/boot/source/i2c/driver/impl/i2c_device_config.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_driver_types.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
struct DeviceConfig {
|
||||
I2cDevice device;
|
||||
Bus bus;
|
||||
u32 slave_address;
|
||||
AddressingMode addressing_mode;
|
||||
SpeedMode speed_mode;
|
||||
u32 max_retries;
|
||||
u64 retry_wait_time;
|
||||
};
|
||||
|
||||
constexpr DeviceConfig g_device_configs[I2cDevice_Count] = {
|
||||
{I2cDevice_DebugPad, Bus::I2C1, 0x52, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
|
||||
{I2cDevice_TouchPanel, Bus::I2C3, 0x49, AddressingMode::SevenBit, SpeedMode::Fast, 0, 0},
|
||||
{I2cDevice_Tmp451, Bus::I2C1, 0x4c, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
|
||||
{I2cDevice_Nct72, Bus::I2C1, 0x4c, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
|
||||
{I2cDevice_Alc5639, Bus::I2C1, 0x1c, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
|
||||
{I2cDevice_Max77620Rtc, Bus::I2C5, 0x68, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Max77620Pmic, Bus::I2C5, 0x3c, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Max77621Cpu, Bus::I2C5, 0x1b, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Max77621Gpu, Bus::I2C5, 0x1c, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Bq24193, Bus::I2C1, 0x6b, AddressingMode::SevenBit, SpeedMode::Normal, 3, 5'000'000},
|
||||
{I2cDevice_Max17050, Bus::I2C1, 0x36, AddressingMode::SevenBit, SpeedMode::Normal, 3, 5'000'000},
|
||||
{I2cDevice_Bm92t30mwv, Bus::I2C1, 0x18, AddressingMode::SevenBit, SpeedMode::Normal, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Vdd15v0Hb, Bus::I2C2, 0x40, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysCpuDs, Bus::I2C2, 0x41, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysGpuDs, Bus::I2C2, 0x44, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysDdrDs, Bus::I2C2, 0x45, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysAp, Bus::I2C2, 0x46, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysBlDs, Bus::I2C2, 0x47, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Bh1730, Bus::I2C2, 0x29, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysCore, Bus::I2C2, 0x48, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Soc1V8, Bus::I2C2, 0x49, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Lpddr1V8, Bus::I2C2, 0x4a, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Reg1V32, Bus::I2C2, 0x4b, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Vdd3V3Sys, Bus::I2C2, 0x4d, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
{I2cDevice_HdmiDdc, Bus::I2C4, 0x50, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
|
||||
{I2cDevice_HdmiScdc, Bus::I2C4, 0x54, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
|
||||
{I2cDevice_HdmiHdcp, Bus::I2C4, 0x3a, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
|
||||
{I2cDevice_Fan53528, Bus::I2C5, 0xa4, AddressingMode::SevenBit, SpeedMode::Fast, 0, 0},
|
||||
{I2cDevice_Max77812_3, Bus::I2C5, 0x31, AddressingMode::SevenBit, SpeedMode::Fast, 0, 0},
|
||||
{I2cDevice_Max77812_2, Bus::I2C5, 0x33, AddressingMode::SevenBit, SpeedMode::Fast, 0, 0},
|
||||
{I2cDevice_Ina226VddDdr0V6, Bus::I2C2, 0x4e, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
|
||||
};
|
||||
|
||||
constexpr size_t NumDeviceConfigs = sizeof(g_device_configs) / sizeof(g_device_configs[0]);
|
||||
|
||||
constexpr size_t DeviceInvalidIndex = static_cast<size_t>(-1);
|
||||
|
||||
size_t GetDeviceIndex(I2cDevice dev) {
|
||||
for (size_t i = 0; i < NumDeviceConfigs; i++) {
|
||||
if (g_device_configs[i].device == dev) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return DeviceInvalidIndex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool IsDeviceSupported(I2cDevice dev) {
|
||||
return GetDeviceIndex(dev) != DeviceInvalidIndex;
|
||||
}
|
||||
|
||||
Bus GetDeviceBus(I2cDevice dev) {
|
||||
const size_t dev_idx = GetDeviceIndex(dev);
|
||||
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].bus;
|
||||
}
|
||||
|
||||
u32 GetDeviceSlaveAddress(I2cDevice dev) {
|
||||
const size_t dev_idx = GetDeviceIndex(dev);
|
||||
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].slave_address;
|
||||
}
|
||||
|
||||
AddressingMode GetDeviceAddressingMode(I2cDevice dev) {
|
||||
const size_t dev_idx = GetDeviceIndex(dev);
|
||||
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].addressing_mode;
|
||||
}
|
||||
|
||||
SpeedMode GetDeviceSpeedMode(I2cDevice dev) {
|
||||
const size_t dev_idx = GetDeviceIndex(dev);
|
||||
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].speed_mode;
|
||||
}
|
||||
|
||||
u32 GetDeviceMaxRetries(I2cDevice dev) {
|
||||
const size_t dev_idx = GetDeviceIndex(dev);
|
||||
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].max_retries;
|
||||
}
|
||||
|
||||
u64 GetDeviceRetryWaitTime(I2cDevice dev) {
|
||||
const size_t dev_idx = GetDeviceIndex(dev);
|
||||
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].retry_wait_time;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../../i2c_types.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
enum class Command {
|
||||
Send = 0,
|
||||
Receive = 1,
|
||||
};
|
||||
|
||||
enum class Bus {
|
||||
I2C1 = 0,
|
||||
I2C2 = 1,
|
||||
I2C3 = 2,
|
||||
I2C4 = 3,
|
||||
I2C5 = 4,
|
||||
I2C6 = 5,
|
||||
Count,
|
||||
};
|
||||
|
||||
/* Bus helpers. */
|
||||
constexpr inline size_t ConvertToIndex(Bus bus) {
|
||||
return static_cast<size_t>(bus);
|
||||
}
|
||||
|
||||
constexpr inline Bus ConvertFromIndex(size_t idx) {
|
||||
if (idx >= static_cast<size_t>(Bus::Count)) {
|
||||
std::abort();
|
||||
}
|
||||
return static_cast<Bus>(idx);
|
||||
}
|
||||
|
||||
constexpr inline PcvModule ConvertToPcvModule(Bus bus) {
|
||||
switch (bus) {
|
||||
case Bus::I2C1:
|
||||
return PcvModule_I2C1;
|
||||
case Bus::I2C2:
|
||||
return PcvModule_I2C2;
|
||||
case Bus::I2C3:
|
||||
return PcvModule_I2C3;
|
||||
case Bus::I2C4:
|
||||
return PcvModule_I2C4;
|
||||
case Bus::I2C5:
|
||||
return PcvModule_I2C5;
|
||||
case Bus::I2C6:
|
||||
return PcvModule_I2C6;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr inline Bus ConvertFromPcvModule(PcvModule module) {
|
||||
switch (module) {
|
||||
case PcvModule_I2C1:
|
||||
return Bus::I2C1;
|
||||
case PcvModule_I2C2:
|
||||
return Bus::I2C2;
|
||||
case PcvModule_I2C3:
|
||||
return Bus::I2C3;
|
||||
case PcvModule_I2C4:
|
||||
return Bus::I2C4;
|
||||
case PcvModule_I2C5:
|
||||
return Bus::I2C5;
|
||||
case PcvModule_I2C6:
|
||||
return Bus::I2C6;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
/* Global type functions. */
|
||||
bool IsDeviceSupported(I2cDevice dev);
|
||||
Bus GetDeviceBus(I2cDevice dev);
|
||||
u32 GetDeviceSlaveAddress(I2cDevice dev);
|
||||
AddressingMode GetDeviceAddressingMode(I2cDevice dev);
|
||||
SpeedMode GetDeviceSpeedMode(I2cDevice dev);
|
||||
u32 GetDeviceMaxRetries(I2cDevice dev);
|
||||
u64 GetDeviceRetryWaitTime(I2cDevice dev);
|
||||
|
||||
}
|
33
stratosphere/boot/source/i2c/driver/impl/i2c_pcv.hpp
Normal file
33
stratosphere/boot/source/i2c/driver/impl/i2c_pcv.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
/* This forward declares the functionality from pcv that i2c::driver uses. */
|
||||
/* This allows for overriding at compile-time (e.g., for boot sysmodule). */
|
||||
namespace sts::pcv {
|
||||
|
||||
void Initialize();
|
||||
void Finalize();
|
||||
Result SetClockRate(PcvModule module, u32 hz);
|
||||
Result SetClockEnabled(PcvModule module, bool enabled);
|
||||
Result SetVoltageEnabled(u32 domain, bool enabled);
|
||||
Result SetVoltageValue(u32 domain, u32 voltage);
|
||||
Result SetReset(PcvModule module, bool reset);
|
||||
|
||||
}
|
109
stratosphere/boot/source/i2c/driver/impl/i2c_registers.hpp
Normal file
109
stratosphere/boot/source/i2c/driver/impl/i2c_registers.hpp
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/reg.hpp>
|
||||
|
||||
#include "i2c_driver_types.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
struct Registers {
|
||||
volatile u32 I2C_I2C_CNFG_0;
|
||||
volatile u32 I2C_I2C_CMD_ADDR0_0;
|
||||
volatile u32 I2C_I2C_CMD_ADDR1_0;
|
||||
volatile u32 I2C_I2C_CMD_DATA1_0;
|
||||
volatile u32 I2C_I2C_CMD_DATA2_0;
|
||||
volatile u32 _0x14;
|
||||
volatile u32 _0x18;
|
||||
volatile u32 I2C_I2C_STATUS_0;
|
||||
volatile u32 I2C_I2C_SL_CNFG_0;
|
||||
volatile u32 I2C_I2C_SL_RCVD_0;
|
||||
volatile u32 I2C_I2C_SL_STATUS_0;
|
||||
volatile u32 I2C_I2C_SL_ADDR1_0;
|
||||
volatile u32 I2C_I2C_SL_ADDR2_0;
|
||||
volatile u32 I2C_I2C_TLOW_SEXT_0;
|
||||
volatile u32 _0x38;
|
||||
volatile u32 I2C_I2C_SL_DELAY_COUNT_0;
|
||||
volatile u32 I2C_I2C_SL_INT_MASK_0;
|
||||
volatile u32 I2C_I2C_SL_INT_SOURCE_0;
|
||||
volatile u32 I2C_I2C_SL_INT_SET_0;
|
||||
volatile u32 _0x4C;
|
||||
volatile u32 I2C_I2C_TX_PACKET_FIFO_0;
|
||||
volatile u32 I2C_I2C_RX_FIFO_0;
|
||||
volatile u32 I2C_PACKET_TRANSFER_STATUS_0;
|
||||
volatile u32 I2C_FIFO_CONTROL_0;
|
||||
volatile u32 I2C_FIFO_STATUS_0;
|
||||
volatile u32 I2C_INTERRUPT_MASK_REGISTER_0;
|
||||
volatile u32 I2C_INTERRUPT_STATUS_REGISTER_0;
|
||||
volatile u32 I2C_I2C_CLK_DIVISOR_REGISTER_0;
|
||||
volatile u32 I2C_I2C_INTERRUPT_SOURCE_REGISTER_0;
|
||||
volatile u32 I2C_I2C_INTERRUPT_SET_REGISTER_0;
|
||||
volatile u32 I2C_I2C_SLV_TX_PACKET_FIFO_0;
|
||||
volatile u32 I2C_I2C_SLV_RX_FIFO_0;
|
||||
volatile u32 I2C_I2C_SLV_PACKET_STATUS_0;
|
||||
volatile u32 I2C_I2C_BUS_CLEAR_CONFIG_0;
|
||||
volatile u32 I2C_I2C_BUS_CLEAR_STATUS_0;
|
||||
volatile u32 I2C_I2C_CONFIG_LOAD_0;
|
||||
volatile u32 _0x90;
|
||||
volatile u32 I2C_I2C_INTERFACE_TIMING_0_0;
|
||||
volatile u32 I2C_I2C_INTERFACE_TIMING_1_0;
|
||||
volatile u32 I2C_I2C_HS_INTERFACE_TIMING_0_0;
|
||||
volatile u32 I2C_I2C_HS_INTERFACE_TIMING_1_0;
|
||||
};
|
||||
|
||||
struct ClkRstRegisters {
|
||||
public:
|
||||
uintptr_t clk_src_reg;
|
||||
uintptr_t clk_en_reg;
|
||||
uintptr_t rst_reg;
|
||||
u32 mask;
|
||||
public:
|
||||
void SetBus(Bus bus) {
|
||||
static constexpr uintptr_t s_clk_src_offsets[ConvertToIndex(Bus::Count)] = {
|
||||
0x124, 0x198, 0x1b8, 0x3c4, 0x128, 0x65c
|
||||
};
|
||||
static constexpr uintptr_t s_clk_en_offsets[ConvertToIndex(Bus::Count)] = {
|
||||
0x010, 0x014, 0x018, 0x360, 0x014, 0x280
|
||||
};
|
||||
static constexpr uintptr_t s_rst_offsets[ConvertToIndex(Bus::Count)] = {
|
||||
0x004, 0x008, 0x00c, 0x358, 0x008, 0x28c
|
||||
};
|
||||
static constexpr size_t s_bit_shifts[ConvertToIndex(Bus::Count)] = {
|
||||
12, 22, 3, 7, 15, 6
|
||||
};
|
||||
|
||||
const uintptr_t registers = GetIoMapping(0x60006000ul, 0x1000);
|
||||
const size_t idx = ConvertToIndex(bus);
|
||||
this->clk_src_reg = registers + s_clk_src_offsets[idx];
|
||||
this->clk_en_reg = registers + s_clk_en_offsets[idx];
|
||||
this->rst_reg = registers + s_rst_offsets[idx];
|
||||
this->mask = (1u << s_bit_shifts[idx]);
|
||||
}
|
||||
};
|
||||
|
||||
inline Registers *GetRegisters(Bus bus) {
|
||||
static constexpr uintptr_t s_offsets[ConvertToIndex(Bus::Count)] = {
|
||||
0x0000, 0x0400, 0x0500, 0x0700, 0x1000, 0x1100
|
||||
};
|
||||
|
||||
const uintptr_t registers = GetIoMapping(0x7000c000ul, 0x2000) + s_offsets[ConvertToIndex(bus)];
|
||||
return reinterpret_cast<Registers *>(registers);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_pcv.hpp"
|
||||
#include "i2c_resource_manager.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
void ResourceManager::Initialize() {
|
||||
std::scoped_lock<HosMutex> lk(this->initialize_mutex);
|
||||
this->ref_cnt++;
|
||||
}
|
||||
|
||||
void ResourceManager::Finalize() {
|
||||
std::scoped_lock<HosMutex> lk(this->initialize_mutex);
|
||||
if (this->ref_cnt == 0) {
|
||||
std::abort();
|
||||
}
|
||||
this->ref_cnt--;
|
||||
if (this->ref_cnt > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::scoped_lock<HosMutex> sess_lk(this->session_open_mutex);
|
||||
for (size_t i = 0; i < MaxDriverSessions; i++) {
|
||||
this->sessions[i].Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t ResourceManager::GetFreeSessionId() const {
|
||||
for (size_t i = 0; i < MaxDriverSessions; i++) {
|
||||
if (!this->sessions[i].IsOpen()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidSessionId;
|
||||
}
|
||||
|
||||
void ResourceManager::OpenSession(driver::Session *out_session, Bus bus, u32 slave_address, AddressingMode addressing_mode, SpeedMode speed_mode, u32 max_retries, u64 retry_wait_time) {
|
||||
bool need_enable_ldo6 = false;
|
||||
size_t session_id = InvalidSessionId;
|
||||
/* Get, open session. */
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
|
||||
if (out_session == nullptr || bus >= Bus::Count) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
session_id = GetFreeSessionId();
|
||||
if (session_id == InvalidSessionId) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
|
||||
if ((bus == Bus::I2C2 || bus == Bus::I2C3) && (this->bus_accessors[ConvertToIndex(Bus::I2C2)].GetOpenSessions() == 0 && this->bus_accessors[ConvertToIndex(Bus::I2C3)].GetOpenSessions() == 0)) {
|
||||
need_enable_ldo6 = true;
|
||||
}
|
||||
|
||||
out_session->session_id = session_id;
|
||||
out_session->bus_idx = ConvertToIndex(bus);
|
||||
this->sessions[session_id].Open(bus, slave_address, addressing_mode, speed_mode, &this->bus_accessors[ConvertToIndex(bus)], max_retries, retry_wait_time);
|
||||
}
|
||||
|
||||
this->sessions[session_id].Start();
|
||||
if (need_enable_ldo6) {
|
||||
pcv::Initialize();
|
||||
if (R_FAILED(pcv::SetVoltageValue(10, 2'900'000))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pcv::SetVoltageEnabled(10, true))) {
|
||||
std::abort();
|
||||
}
|
||||
pcv::Finalize();
|
||||
svcSleepThread(560'000ul);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::CloseSession(const driver::Session &session) {
|
||||
bool need_disable_ldo6 = false;
|
||||
/* Get, open session. */
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
|
||||
if (!this->sessions[session.session_id].IsOpen()) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
this->sessions[session.session_id].Close();
|
||||
|
||||
if ((ConvertFromIndex(session.bus_idx) == Bus::I2C2 || ConvertFromIndex(session.bus_idx) == Bus::I2C3) &&
|
||||
(this->bus_accessors[ConvertToIndex(Bus::I2C2)].GetOpenSessions() == 0 && this->bus_accessors[ConvertToIndex(Bus::I2C3)].GetOpenSessions() == 0)) {
|
||||
need_disable_ldo6 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_disable_ldo6) {
|
||||
pcv::Initialize();
|
||||
if (R_FAILED(pcv::SetVoltageEnabled(10, false))) {
|
||||
std::abort();
|
||||
}
|
||||
pcv::Finalize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ResourceManager::SuspendBuses() {
|
||||
if (this->ref_cnt == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (!this->suspended) {
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
|
||||
this->suspended = true;
|
||||
for (size_t i = 0; i < ConvertToIndex(Bus::Count); i++) {
|
||||
if (i != PowerBusId && this->bus_accessors[i].GetOpenSessions() > 0) {
|
||||
this->bus_accessors[i].Suspend();
|
||||
}
|
||||
}
|
||||
}
|
||||
pcv::Initialize();
|
||||
if (R_FAILED(pcv::SetVoltageEnabled(10, false))) {
|
||||
std::abort();
|
||||
}
|
||||
pcv::Finalize();
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::ResumeBuses() {
|
||||
if (this->ref_cnt == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (this->suspended) {
|
||||
if (this->bus_accessors[ConvertToIndex(Bus::I2C2)].GetOpenSessions() > 0 || this->bus_accessors[ConvertToIndex(Bus::I2C3)].GetOpenSessions() > 0) {
|
||||
pcv::Initialize();
|
||||
if (R_FAILED(pcv::SetVoltageValue(10, 2'900'000))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(pcv::SetVoltageEnabled(10, true))) {
|
||||
std::abort();
|
||||
}
|
||||
pcv::Finalize();
|
||||
svcSleepThread(1'560'000ul);
|
||||
}
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
|
||||
for (size_t i = 0; i < ConvertToIndex(Bus::Count); i++) {
|
||||
if (i != PowerBusId && this->bus_accessors[i].GetOpenSessions() > 0) {
|
||||
this->bus_accessors[i].Resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
this->suspended = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::SuspendPowerBus() {
|
||||
if (this->ref_cnt == 0) {
|
||||
std::abort();
|
||||
}
|
||||
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
|
||||
|
||||
if (!this->power_bus_suspended) {
|
||||
this->power_bus_suspended = true;
|
||||
if (this->bus_accessors[PowerBusId].GetOpenSessions() > 0) {
|
||||
this->bus_accessors[PowerBusId].Suspend();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::ResumePowerBus() {
|
||||
if (this->ref_cnt == 0) {
|
||||
std::abort();
|
||||
}
|
||||
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
|
||||
|
||||
if (this->power_bus_suspended) {
|
||||
if (this->bus_accessors[PowerBusId].GetOpenSessions() > 0) {
|
||||
this->bus_accessors[PowerBusId].Resume();
|
||||
}
|
||||
this->power_bus_suspended = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../i2c_api.hpp"
|
||||
#include "i2c_driver_types.hpp"
|
||||
#include "i2c_bus_accessor.hpp"
|
||||
#include "i2c_session.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
class ResourceManager {
|
||||
public:
|
||||
static constexpr size_t MaxDriverSessions = 40;
|
||||
static constexpr size_t PowerBusId = ConvertToIndex(Bus::I2C5);
|
||||
static constexpr size_t InvalidSessionId = static_cast<size_t>(-1);
|
||||
private:
|
||||
HosMutex initialize_mutex;
|
||||
HosMutex session_open_mutex;
|
||||
size_t ref_cnt = 0;
|
||||
bool suspended = false;
|
||||
bool power_bus_suspended = false;
|
||||
Session sessions[MaxDriverSessions];
|
||||
BusAccessor bus_accessors[ConvertToIndex(Bus::Count)];
|
||||
HosMutex transaction_mutexes[ConvertToIndex(Bus::Count)];
|
||||
public:
|
||||
ResourceManager() {
|
||||
/* ... */
|
||||
}
|
||||
private:
|
||||
size_t GetFreeSessionId() const;
|
||||
public:
|
||||
/* N uses a singleton here, we'll oblige. */
|
||||
static ResourceManager &GetInstance() {
|
||||
static ResourceManager s_instance;
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
bool IsInitialized() const {
|
||||
return this->ref_cnt > 0;
|
||||
}
|
||||
|
||||
Session& GetSession(size_t id) {
|
||||
return this->sessions[id];
|
||||
}
|
||||
|
||||
HosMutex& GetTransactionMutex(Bus bus) {
|
||||
return this->transaction_mutexes[ConvertToIndex(bus)];
|
||||
}
|
||||
|
||||
void Initialize();
|
||||
void Finalize();
|
||||
|
||||
void OpenSession(driver::Session *out_session, Bus bus, u32 slave_address, AddressingMode addressing_mode, SpeedMode speed_mode, u32 max_retries, u64 retry_wait_time);
|
||||
void CloseSession(const driver::Session &session);
|
||||
void SuspendBuses();
|
||||
void ResumeBuses();
|
||||
void SuspendPowerBus();
|
||||
void ResumePowerBus();
|
||||
};
|
||||
|
||||
}
|
103
stratosphere/boot/source/i2c/driver/impl/i2c_session.cpp
Normal file
103
stratosphere/boot/source/i2c/driver/impl/i2c_session.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_session.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
void Session::Open(Bus bus, u32 slave_address, AddressingMode addr_mode, SpeedMode speed_mode, BusAccessor *bus_accessor, u32 max_retries, u64 retry_wait_time) {
|
||||
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
|
||||
if (!this->open) {
|
||||
this->bus_accessor = bus_accessor;
|
||||
this->bus = bus;
|
||||
this->slave_address = slave_address;
|
||||
this->addressing_mode = addr_mode;
|
||||
this->max_retries = max_retries;
|
||||
this->retry_wait_time = retry_wait_time;
|
||||
this->bus_accessor->Open(this->bus, speed_mode);
|
||||
this->open = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Session::Start() {
|
||||
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
|
||||
if (this->open) {
|
||||
if (this->bus_accessor->GetOpenSessions() == 1) {
|
||||
this->bus_accessor->DoInitialConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Session::Close() {
|
||||
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
|
||||
if (this->open) {
|
||||
this->bus_accessor->Close();
|
||||
this->bus_accessor = nullptr;
|
||||
this->open = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Session::IsOpen() const {
|
||||
return this->open;
|
||||
}
|
||||
|
||||
Result Session::DoTransaction(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, Command command) {
|
||||
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
|
||||
|
||||
if (this->bus_accessor->GetBusy()) {
|
||||
return ResultI2cBusBusy;
|
||||
}
|
||||
|
||||
this->bus_accessor->OnStartTransaction();
|
||||
ON_SCOPE_EXIT { this->bus_accessor->OnStopTransaction(); };
|
||||
|
||||
R_TRY(this->bus_accessor->StartTransaction(command, this->addressing_mode, this->slave_address));
|
||||
|
||||
switch (command) {
|
||||
case Command::Send:
|
||||
R_TRY(this->bus_accessor->Send(reinterpret_cast<const u8 *>(src), num_bytes, option, this->addressing_mode, this->slave_address));
|
||||
break;
|
||||
case Command::Receive:
|
||||
R_TRY(this->bus_accessor->Receive(reinterpret_cast<u8 *>(dst), num_bytes, option, this->addressing_mode, this->slave_address));
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Session::DoTransactionWithRetry(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, Command command) {
|
||||
size_t i = 0;
|
||||
while (true) {
|
||||
R_TRY_CATCH(this->DoTransaction(dst, src, num_bytes, option, command)) {
|
||||
R_CATCH(ResultI2cTimedOut) {
|
||||
i++;
|
||||
if (i <= this->max_retries) {
|
||||
svcSleepThread(this->retry_wait_time);
|
||||
continue;
|
||||
}
|
||||
return ResultI2cBusBusy;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
51
stratosphere/boot/source/i2c/driver/impl/i2c_session.hpp
Normal file
51
stratosphere/boot/source/i2c/driver/impl/i2c_session.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_driver_types.hpp"
|
||||
#include "i2c_bus_accessor.hpp"
|
||||
|
||||
namespace sts::i2c::driver::impl {
|
||||
|
||||
class Session {
|
||||
private:
|
||||
HosMutex bus_accessor_mutex;
|
||||
BusAccessor *bus_accessor = nullptr;
|
||||
Bus bus = Bus::I2C1;
|
||||
u32 slave_address = 0;
|
||||
AddressingMode addressing_mode = AddressingMode::SevenBit;
|
||||
u32 max_retries = 0;
|
||||
u64 retry_wait_time = 0;
|
||||
bool open = false;
|
||||
public:
|
||||
Session() { /* ... */ }
|
||||
public:
|
||||
void Open(Bus bus, u32 slave_address, AddressingMode addr_mode, SpeedMode speed_mode, BusAccessor *bus_accessor, u32 max_retries, u64 retry_wait_time);
|
||||
void Start();
|
||||
void Close();
|
||||
|
||||
bool IsOpen() const;
|
||||
|
||||
Result DoTransaction(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, Command command);
|
||||
Result DoTransactionWithRetry(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, Command command);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
83
stratosphere/boot/source/i2c/i2c_command_list.cpp
Normal file
83
stratosphere/boot/source/i2c/i2c_command_list.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_types.hpp"
|
||||
#include "i2c_command_list.hpp"
|
||||
|
||||
namespace sts::i2c {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Useful definitions. */
|
||||
constexpr size_t SendCommandSize = 2;
|
||||
constexpr size_t ReceiveCommandSize = 2;
|
||||
constexpr size_t SleepCommandSize = 2;
|
||||
|
||||
}
|
||||
|
||||
Result CommandListFormatter::CanEnqueue(size_t size) const {
|
||||
if (this->cmd_list_size - this->cur_index < size) {
|
||||
return ResultI2cFullCommandList;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CommandListFormatter::EnqueueSendCommand(I2cTransactionOption option, const void *src, size_t size) {
|
||||
R_TRY(this->CanEnqueue(SendCommandSize + size));
|
||||
|
||||
this->cmd_list[this->cur_index] = static_cast<u8>(Command::Send);
|
||||
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Start) != 0) << 6;
|
||||
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Stop) != 0) << 7;
|
||||
this->cur_index++;
|
||||
|
||||
this->cmd_list[this->cur_index++] = size;
|
||||
|
||||
const u8 *src_u8 = reinterpret_cast<const u8 *>(src);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
this->cmd_list[this->cur_index++] = src_u8[i];
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CommandListFormatter::EnqueueReceiveCommand(I2cTransactionOption option, size_t size) {
|
||||
R_TRY(this->CanEnqueue(ReceiveCommandSize));
|
||||
|
||||
this->cmd_list[this->cur_index] = static_cast<u8>(Command::Receive);
|
||||
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Start) != 0) << 6;
|
||||
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Stop) != 0) << 7;
|
||||
this->cur_index++;
|
||||
|
||||
this->cmd_list[this->cur_index++] = size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CommandListFormatter::EnqueueSleepCommand(size_t us) {
|
||||
R_TRY(this->CanEnqueue(SleepCommandSize));
|
||||
|
||||
this->cmd_list[this->cur_index] = static_cast<u8>(Command::SubCommand);
|
||||
this->cmd_list[this->cur_index] |= static_cast<u8>(SubCommand::Sleep) << 2;
|
||||
this->cur_index++;
|
||||
|
||||
this->cmd_list[this->cur_index++] = us;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
66
stratosphere/boot/source/i2c/i2c_command_list.hpp
Normal file
66
stratosphere/boot/source/i2c/i2c_command_list.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_types.hpp"
|
||||
|
||||
namespace sts::i2c {
|
||||
|
||||
enum class Command {
|
||||
Send = 0,
|
||||
Receive = 1,
|
||||
SubCommand = 2,
|
||||
Count,
|
||||
};
|
||||
|
||||
enum class SubCommand {
|
||||
Sleep = 0,
|
||||
Count,
|
||||
};
|
||||
|
||||
class CommandListFormatter {
|
||||
public:
|
||||
static constexpr size_t MaxCommandListSize = 0x100;
|
||||
private:
|
||||
u8 *cmd_list = nullptr;
|
||||
size_t cmd_list_size = 0;
|
||||
size_t cur_index = 0;
|
||||
public:
|
||||
CommandListFormatter(void *cmd_list, size_t cmd_list_size) : cmd_list(static_cast<u8 *>(cmd_list)), cmd_list_size(cmd_list_size) {
|
||||
if (cmd_list_size > MaxCommandListSize) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
~CommandListFormatter() {
|
||||
this->cmd_list = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Result CanEnqueue(size_t size) const;
|
||||
public:
|
||||
size_t GetCurrentSize() const {
|
||||
return this->cur_index;
|
||||
}
|
||||
|
||||
Result EnqueueSendCommand(I2cTransactionOption option, const void *src, size_t size);
|
||||
Result EnqueueReceiveCommand(I2cTransactionOption option, size_t size);
|
||||
Result EnqueueSleepCommand(size_t us);
|
||||
};
|
||||
|
||||
}
|
34
stratosphere/boot/source/i2c/i2c_types.hpp
Normal file
34
stratosphere/boot/source/i2c/i2c_types.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::i2c {
|
||||
|
||||
enum class AddressingMode {
|
||||
SevenBit = 0,
|
||||
};
|
||||
|
||||
enum class SpeedMode {
|
||||
Normal = 100000,
|
||||
Fast = 400000,
|
||||
FastPlus = 1000000,
|
||||
HighSpeed = 3400000,
|
||||
};
|
||||
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_types.hpp"
|
||||
#include "i2c_registers.hpp"
|
||||
#include "boot_pcv.hpp"
|
||||
|
||||
static I2cBus GetI2cBus(PcvModule module) {
|
||||
switch (module) {
|
||||
case PcvModule_I2C1:
|
||||
return I2cBus_I2c1;
|
||||
case PcvModule_I2C2:
|
||||
return I2cBus_I2c2;
|
||||
case PcvModule_I2C3:
|
||||
return I2cBus_I2c3;
|
||||
case PcvModule_I2C4:
|
||||
return I2cBus_I2c4;
|
||||
case PcvModule_I2C5:
|
||||
return I2cBus_I2c5;
|
||||
case PcvModule_I2C6:
|
||||
return I2cBus_I2c6;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void Pcv::Initialize() {
|
||||
/* Don't do anything. */
|
||||
}
|
||||
|
||||
void Pcv::Finalize() {
|
||||
/* Don't do anything. */
|
||||
}
|
||||
|
||||
Result Pcv::SetClockRate(PcvModule module, u32 hz) {
|
||||
/* Get clock/reset registers. */
|
||||
ClkRstRegisters regs;
|
||||
regs.SetBus(GetI2cBus(module));
|
||||
/* Set clock enabled/source. */
|
||||
SetRegisterBits(regs.clk_en_reg, regs.mask);
|
||||
ReadWriteRegisterBits(regs.clk_src_reg, 0x4, 0xFF);
|
||||
svcSleepThread(1000ul);
|
||||
ReadWriteRegisterBits(regs.clk_src_reg, 0, 0xE0000000);
|
||||
svcSleepThread(2000ul);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pcv::SetClockEnabled(PcvModule module, bool enabled) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pcv::SetVoltageEnabled(u32 domain, bool enabled) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pcv::SetVoltageValue(u32 domain, u32 voltage) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pcv::SetReset(PcvModule module, bool reset) {
|
||||
/* Get clock/reset registers. */
|
||||
ClkRstRegisters regs;
|
||||
regs.SetBus(GetI2cBus(module));
|
||||
|
||||
/* Set/clear reset. */
|
||||
if (reset) {
|
||||
SetRegisterBits(regs.rst_reg, regs.mask);
|
||||
} else {
|
||||
ClearRegisterBits(regs.rst_reg, ~regs.mask);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_api.hpp"
|
||||
#include "i2c_resource_manager.hpp"
|
||||
|
||||
typedef Result (*CommandHandler)(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session);
|
||||
|
||||
static Result I2cSendHandler(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session) {
|
||||
I2cTransactionOption option = static_cast<I2cTransactionOption>(
|
||||
(((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0)
|
||||
);
|
||||
(*cur_cmd)++;
|
||||
|
||||
size_t num_bytes = (**cur_cmd);
|
||||
(*cur_cmd)++;
|
||||
|
||||
R_TRY(I2cDriver::Send(session, *cur_cmd, num_bytes, option));
|
||||
(*cur_cmd) += num_bytes;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static Result I2cReceiveHandler(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session) {
|
||||
I2cTransactionOption option = static_cast<I2cTransactionOption>(
|
||||
(((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0)
|
||||
);
|
||||
(*cur_cmd)++;
|
||||
|
||||
size_t num_bytes = (**cur_cmd);
|
||||
(*cur_cmd)++;
|
||||
|
||||
R_TRY(I2cDriver::Receive(session, *cur_dst, num_bytes, option));
|
||||
(*cur_dst) += num_bytes;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static Result I2cSubCommandHandler(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session) {
|
||||
const I2cSubCommand sub_cmd = static_cast<I2cSubCommand>((**cur_cmd) >> 2);
|
||||
(*cur_cmd)++;
|
||||
|
||||
switch (sub_cmd) {
|
||||
case I2cSubCommand_Sleep:
|
||||
{
|
||||
const size_t us = (**cur_cmd);
|
||||
(*cur_cmd)++;
|
||||
svcSleepThread(us * 1'000ul);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static constexpr CommandHandler g_cmd_handlers[I2cCommand_Count] = {
|
||||
I2cSendHandler,
|
||||
I2cReceiveHandler,
|
||||
I2cSubCommandHandler,
|
||||
};
|
||||
|
||||
static inline I2cResourceManager &GetResourceManager() {
|
||||
return I2cResourceManager::GetInstance();
|
||||
}
|
||||
|
||||
static inline void CheckInitialized() {
|
||||
if (!GetResourceManager().IsInitialized()) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void I2cDriver::Initialize() {
|
||||
GetResourceManager().Initialize();
|
||||
}
|
||||
|
||||
void I2cDriver::Finalize() {
|
||||
GetResourceManager().Finalize();
|
||||
}
|
||||
|
||||
void I2cDriver::OpenSession(I2cSessionImpl *out_session, I2cDevice device) {
|
||||
CheckInitialized();
|
||||
if (!IsI2cDeviceSupported(device)) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
const I2cBus bus = GetI2cDeviceBus(device);
|
||||
const u32 slave_address = GetI2cDeviceSlaveAddress(device);
|
||||
const AddressingMode addressing_mode = GetI2cDeviceAddressingMode(device);
|
||||
const SpeedMode speed_mode = GetI2cDeviceSpeedMode(device);
|
||||
const u32 max_retries = GetI2cDeviceMaxRetries(device);
|
||||
const u64 retry_wait_time = GetI2cDeviceRetryWaitTime(device);
|
||||
GetResourceManager().OpenSession(out_session, bus, slave_address, addressing_mode, speed_mode, max_retries, retry_wait_time);
|
||||
}
|
||||
|
||||
void I2cDriver::CloseSession(I2cSessionImpl &session) {
|
||||
CheckInitialized();
|
||||
GetResourceManager().CloseSession(session);
|
||||
}
|
||||
|
||||
Result I2cDriver::Send(I2cSessionImpl &session, const void *src, size_t size, I2cTransactionOption option) {
|
||||
CheckInitialized();
|
||||
if (src == nullptr || size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::scoped_lock<HosMutex &> lk(GetResourceManager().GetTransactionMutex(session.bus));
|
||||
return GetResourceManager().GetSession(session.session_id).DoTransactionWithRetry(nullptr, src, size, option, DriverCommand_Send);
|
||||
}
|
||||
|
||||
Result I2cDriver::Receive(I2cSessionImpl &session, void *dst, size_t size, I2cTransactionOption option) {
|
||||
CheckInitialized();
|
||||
if (dst == nullptr || size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
std::scoped_lock<HosMutex &> lk(GetResourceManager().GetTransactionMutex(session.bus));
|
||||
return GetResourceManager().GetSession(session.session_id).DoTransactionWithRetry(dst, nullptr, size, option, DriverCommand_Receive);
|
||||
}
|
||||
|
||||
Result I2cDriver::ExecuteCommandList(I2cSessionImpl &session, void *dst, size_t size, const void *cmd_list, size_t cmd_list_size) {
|
||||
CheckInitialized();
|
||||
if (dst == nullptr || size == 0 || cmd_list == nullptr || cmd_list_size == 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
u8 *cur_dst = static_cast<u8 *>(dst);
|
||||
const u8 *cur_cmd = static_cast<const u8 *>(cmd_list);
|
||||
const u8 *cmd_list_end = cur_cmd + cmd_list_size;
|
||||
|
||||
while (cur_cmd < cmd_list_end) {
|
||||
I2cCommand cmd = static_cast<I2cCommand>((*cur_cmd) & 3);
|
||||
if (cmd >= I2cCommand_Count) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
R_TRY(g_cmd_handlers[cmd](&cur_cmd, &cur_dst, session));
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void I2cDriver::SuspendBuses() {
|
||||
GetResourceManager().SuspendBuses();
|
||||
}
|
||||
|
||||
void I2cDriver::ResumeBuses() {
|
||||
GetResourceManager().ResumeBuses();
|
||||
}
|
||||
|
||||
void I2cDriver::SuspendPowerBus() {
|
||||
GetResourceManager().SuspendPowerBus();
|
||||
}
|
||||
|
||||
void I2cDriver::ResumePowerBus() {
|
||||
GetResourceManager().ResumePowerBus();
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_types.hpp"
|
||||
#include "i2c_command_list.hpp"
|
||||
|
||||
class I2cDriver {
|
||||
public:
|
||||
static void Initialize();
|
||||
static void Finalize();
|
||||
static void OpenSession(I2cSessionImpl *out_session, I2cDevice device);
|
||||
static void CloseSession(I2cSessionImpl &session);
|
||||
static Result Send(I2cSessionImpl &session, const void *src, size_t size, I2cTransactionOption option);
|
||||
static Result Receive(I2cSessionImpl &session, void *dst, size_t size, I2cTransactionOption option);
|
||||
static Result ExecuteCommandList(I2cSessionImpl &session, void *dst, size_t size, const void *cmd_list, size_t cmd_list_size);
|
||||
|
||||
static void SuspendBuses();
|
||||
static void ResumeBuses();
|
||||
static void SuspendPowerBus();
|
||||
static void ResumePowerBus();
|
||||
};
|
@ -1,495 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_bus_accessor.hpp"
|
||||
#include "boot_pcv.hpp"
|
||||
|
||||
void I2cBusAccessor::Open(I2cBus bus, SpeedMode speed_mode) {
|
||||
std::scoped_lock<HosMutex> lk(this->open_mutex);
|
||||
/* Open new session. */
|
||||
this->open_sessions++;
|
||||
|
||||
/* Ensure we're good if this isn't our first session. */
|
||||
if (this->open_sessions > 1) {
|
||||
if (this->speed_mode != speed_mode) {
|
||||
std::abort();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set all members for chosen bus. */
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(this->register_mutex);
|
||||
/* Set bus/registers. */
|
||||
this->SetBus(bus);
|
||||
/* Set pcv module. */
|
||||
switch (bus) {
|
||||
case I2cBus_I2c1:
|
||||
this->pcv_module = PcvModule_I2C1;
|
||||
break;
|
||||
case I2cBus_I2c2:
|
||||
this->pcv_module = PcvModule_I2C2;
|
||||
break;
|
||||
case I2cBus_I2c3:
|
||||
this->pcv_module = PcvModule_I2C3;
|
||||
break;
|
||||
case I2cBus_I2c4:
|
||||
this->pcv_module = PcvModule_I2C4;
|
||||
break;
|
||||
case I2cBus_I2c5:
|
||||
this->pcv_module = PcvModule_I2C5;
|
||||
break;
|
||||
case I2cBus_I2c6:
|
||||
this->pcv_module = PcvModule_I2C6;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
/* Set speed mode. */
|
||||
this->speed_mode = speed_mode;
|
||||
/* Setup interrupt event. */
|
||||
this->CreateInterruptEvent(bus);
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::Close() {
|
||||
std::scoped_lock<HosMutex> lk(this->open_mutex);
|
||||
/* Close current session. */
|
||||
this->open_sessions--;
|
||||
if (this->open_sessions > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Close interrupt event. */
|
||||
eventClose(&this->interrupt_event);
|
||||
|
||||
/* Close PCV. */
|
||||
Pcv::Finalize();
|
||||
|
||||
this->suspended = false;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::Suspend() {
|
||||
std::scoped_lock<HosMutex> lk(this->open_mutex);
|
||||
std::scoped_lock<HosMutex> lk_reg(this->register_mutex);
|
||||
|
||||
if (!this->suspended) {
|
||||
this->suspended = true;
|
||||
|
||||
if (this->pcv_module != PcvModule_I2C5) {
|
||||
this->DisableClock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::Resume() {
|
||||
if (this->suspended) {
|
||||
this->DoInitialConfig();
|
||||
this->suspended = false;
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::DoInitialConfig() {
|
||||
std::scoped_lock<HosMutex> lk(this->register_mutex);
|
||||
|
||||
if (this->pcv_module != PcvModule_I2C5) {
|
||||
Pcv::Initialize();
|
||||
}
|
||||
|
||||
this->ResetController();
|
||||
this->SetClock(this->speed_mode);
|
||||
this->SetPacketMode();
|
||||
this->FlushFifos();
|
||||
}
|
||||
|
||||
size_t I2cBusAccessor::GetOpenSessions() const {
|
||||
return this->open_sessions;
|
||||
}
|
||||
|
||||
bool I2cBusAccessor::GetBusy() const {
|
||||
/* Nintendo has a loop here that calls a member function to check if busy, retrying a few times. */
|
||||
/* This member function does "return false". */
|
||||
/* We will not bother with the loop. */
|
||||
return false;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::OnStartTransaction() const {
|
||||
/* Nothing actually happens here. */
|
||||
}
|
||||
|
||||
void I2cBusAccessor::OnStopTransaction() const {
|
||||
/* Nothing actually happens here. */
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::StartTransaction(DriverCommand command, AddressingMode addressing_mode, u32 slave_address) {
|
||||
/* Nothing actually happens here... */
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
|
||||
std::scoped_lock<HosMutex> lk(this->register_mutex);
|
||||
const u8 *cur_src = data;
|
||||
size_t remaining = num_bytes;
|
||||
|
||||
/* Set interrupt enable, clear interrupt status. */
|
||||
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8E);
|
||||
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0, 0xFC);
|
||||
|
||||
ON_SCOPE_EXIT { this->ClearInterruptMask(); };
|
||||
|
||||
/* Send header. */
|
||||
this->WriteTransferHeader(TransferMode_Send, option, addressing_mode, slave_address, num_bytes);
|
||||
|
||||
/* Send bytes. */
|
||||
while (true) {
|
||||
const u32 fifo_status = ReadRegister(&this->i2c_registers->I2C_FIFO_STATUS_0);
|
||||
const size_t fifo_cnt = (fifo_status >> 4);
|
||||
|
||||
for (size_t fifo_idx = 0; remaining > 0 && fifo_idx < fifo_cnt; fifo_idx++) {
|
||||
const size_t cur_bytes = std::min(remaining, sizeof(u32));
|
||||
u32 val = 0;
|
||||
for (size_t i = 0; i < cur_bytes; i++) {
|
||||
val |= cur_src[i] << (8 * i);
|
||||
}
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, val);
|
||||
|
||||
cur_src += cur_bytes;
|
||||
remaining -= cur_bytes;
|
||||
}
|
||||
|
||||
if (remaining == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
eventClear(&this->interrupt_event);
|
||||
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
|
||||
this->HandleTransactionResult(ResultI2cBusBusy);
|
||||
eventClear(&this->interrupt_event);
|
||||
return ResultI2cTimedOut;
|
||||
}
|
||||
|
||||
R_TRY(this->GetAndHandleTransactionResult());
|
||||
}
|
||||
|
||||
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8C);
|
||||
|
||||
/* Wait for successful completion. */
|
||||
while (true) {
|
||||
R_TRY(this->GetAndHandleTransactionResult());
|
||||
|
||||
/* Check PACKET_XFER_COMPLETE */
|
||||
const u32 interrupt_status = ReadRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
|
||||
if (interrupt_status & 0x80) {
|
||||
R_TRY(this->GetAndHandleTransactionResult());
|
||||
break;
|
||||
}
|
||||
|
||||
eventClear(&this->interrupt_event);
|
||||
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
|
||||
this->HandleTransactionResult(ResultI2cBusBusy);
|
||||
eventClear(&this->interrupt_event);
|
||||
return ResultI2cTimedOut;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::Receive(u8 *out_data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
|
||||
std::scoped_lock<HosMutex> lk(this->register_mutex);
|
||||
u8 *cur_dst = out_data;
|
||||
size_t remaining = num_bytes;
|
||||
|
||||
/* Set interrupt enable, clear interrupt status. */
|
||||
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8D);
|
||||
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0, 0xFC);
|
||||
|
||||
/* Send header. */
|
||||
this->WriteTransferHeader(TransferMode_Receive, option, addressing_mode, slave_address, num_bytes);
|
||||
|
||||
/* Receive bytes. */
|
||||
while (remaining > 0) {
|
||||
eventClear(&this->interrupt_event);
|
||||
if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
|
||||
this->HandleTransactionResult(ResultI2cBusBusy);
|
||||
this->ClearInterruptMask();
|
||||
eventClear(&this->interrupt_event);
|
||||
return ResultI2cTimedOut;
|
||||
}
|
||||
|
||||
R_TRY(this->GetAndHandleTransactionResult());
|
||||
|
||||
const u32 fifo_status = ReadRegister(&this->i2c_registers->I2C_FIFO_STATUS_0);
|
||||
const size_t fifo_cnt = std::min((remaining + 3) >> 2, static_cast<size_t>(fifo_status & 0xF));
|
||||
|
||||
for (size_t fifo_idx = 0; remaining > 0 && fifo_idx < fifo_cnt; fifo_idx++) {
|
||||
const u32 val = ReadRegister(&this->i2c_registers->I2C_I2C_RX_FIFO_0);
|
||||
const size_t cur_bytes = std::min(remaining, sizeof(u32));
|
||||
for (size_t i = 0; i < cur_bytes; i++) {
|
||||
cur_dst[i] = static_cast<u8>((val >> (8 * i)) & 0xFF);
|
||||
}
|
||||
|
||||
cur_dst += cur_bytes;
|
||||
remaining -= cur_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/* N doesn't do ClearInterruptMask. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::SetBus(I2cBus bus) {
|
||||
this->bus = bus;
|
||||
this->i2c_registers = GetI2cRegisters(bus);
|
||||
this->clkrst_registers.SetBus(bus);
|
||||
}
|
||||
|
||||
void I2cBusAccessor::CreateInterruptEvent(I2cBus bus) {
|
||||
static constexpr u64 s_interrupts[] = {
|
||||
0x46, 0x74, 0x7C, 0x98, 0x55, 0x5F
|
||||
};
|
||||
if (static_cast<size_t>(bus) >= sizeof(s_interrupts) / sizeof(s_interrupts[0])) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Handle evt_h;
|
||||
if (R_FAILED(svcCreateInterruptEvent(&evt_h, s_interrupts[static_cast<size_t>(bus)], 1))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
eventLoadRemote(&this->interrupt_event, evt_h, false);
|
||||
}
|
||||
|
||||
void I2cBusAccessor::SetClock(SpeedMode speed_mode) {
|
||||
u32 t_high, t_low;
|
||||
u32 clk_div, src_div;
|
||||
u32 debounce;
|
||||
|
||||
switch (speed_mode) {
|
||||
case SpeedMode_Normal:
|
||||
t_high = 2;
|
||||
t_low = 4;
|
||||
clk_div = 0x19;
|
||||
src_div = 0x13;
|
||||
debounce = 2;
|
||||
break;
|
||||
case SpeedMode_Fast:
|
||||
t_high = 2;
|
||||
t_low = 4;
|
||||
clk_div = 0x19;
|
||||
src_div = 0x04;
|
||||
debounce = 2;
|
||||
break;
|
||||
case SpeedMode_FastPlus:
|
||||
t_high = 2;
|
||||
t_low = 4;
|
||||
clk_div = 0x10;
|
||||
src_div = 0x02;
|
||||
debounce = 0;
|
||||
break;
|
||||
case SpeedMode_HighSpeed:
|
||||
t_high = 3;
|
||||
t_low = 8;
|
||||
clk_div = 0x02;
|
||||
src_div = 0x02;
|
||||
debounce = 0;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (speed_mode == SpeedMode_HighSpeed) {
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_HS_INTERFACE_TIMING_0_0, (t_high << 8) | (t_low));
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_CLK_DIVISOR_REGISTER_0, clk_div);
|
||||
} else {
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_INTERFACE_TIMING_0_0, (t_high << 8) | (t_low));
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_CLK_DIVISOR_REGISTER_0, (clk_div << 16));
|
||||
}
|
||||
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_CNFG_0, debounce);
|
||||
ReadRegister(&this->i2c_registers->I2C_I2C_CNFG_0);
|
||||
|
||||
if (this->pcv_module != PcvModule_I2C5) {
|
||||
if (R_FAILED(Pcv::SetReset(this->pcv_module, true))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(Pcv::SetClockRate(this->pcv_module, (408'000'000) / (src_div + 1)))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(Pcv::SetReset(this->pcv_module, false))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::ResetController() const {
|
||||
if (this->pcv_module != PcvModule_I2C5) {
|
||||
if (R_FAILED(Pcv::SetReset(this->pcv_module, true))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(Pcv::SetClockRate(this->pcv_module, 81'600'000))) {
|
||||
std::abort();
|
||||
}
|
||||
if (R_FAILED(Pcv::SetReset(this->pcv_module, false))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::ClearBus() const {
|
||||
bool success = false;
|
||||
for (size_t i = 0; i < 3 && !success; i++) {
|
||||
success = true;
|
||||
|
||||
this->ResetController();
|
||||
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x90000);
|
||||
SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x4);
|
||||
SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x2);
|
||||
|
||||
SetRegisterBits(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0, 0x1);
|
||||
{
|
||||
u64 start_tick = armGetSystemTick();
|
||||
while (ReadRegister(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0) & 1) {
|
||||
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x1);
|
||||
{
|
||||
u64 start_tick = armGetSystemTick();
|
||||
while (ReadRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0) & 1) {
|
||||
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
u64 start_tick = armGetSystemTick();
|
||||
while (ReadRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_STATUS_0) & 1) {
|
||||
if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::DisableClock() {
|
||||
if (R_FAILED(Pcv::SetClockEnabled(this->pcv_module, false))) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::SetPacketMode() {
|
||||
/* Set PACKET_MODE_EN, MSTR_CONFIG_LOAD */
|
||||
SetRegisterBits(&this->i2c_registers->I2C_I2C_CNFG_0, 0x400);
|
||||
SetRegisterBits(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0, 0x1);
|
||||
|
||||
/* Set TX_FIFO_TRIGGER, RX_FIFO_TRIGGER */
|
||||
WriteRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0, 0xFC);
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::FlushFifos() {
|
||||
WriteRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0, 0xFF);
|
||||
|
||||
/* Wait for flush to finish, check every ms for 5 ms. */
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
if (!(ReadRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0) & 3)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
svcSleepThread(1'000'000ul);
|
||||
}
|
||||
|
||||
return ResultI2cBusBusy;
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::GetTransactionResult() const {
|
||||
const u32 packet_status = ReadRegister(&this->i2c_registers->I2C_PACKET_TRANSFER_STATUS_0);
|
||||
const u32 interrupt_status = ReadRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
|
||||
|
||||
/* Check for no ack. */
|
||||
if ((packet_status & 0xC) || (interrupt_status & 0x8)) {
|
||||
return ResultI2cNoAck;
|
||||
}
|
||||
|
||||
/* Check for arb lost. */
|
||||
if ((packet_status & 0x2) || (interrupt_status & 0x4)) {
|
||||
this->ClearBus();
|
||||
return ResultI2cBusBusy;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::HandleTransactionResult(Result result) {
|
||||
if (R_FAILED(result)) {
|
||||
if (result == ResultI2cNoAck || result == ResultI2cBusBusy) {
|
||||
this->ResetController();
|
||||
this->SetClock(this->speed_mode);
|
||||
this->SetPacketMode();
|
||||
this->FlushFifos();
|
||||
} else {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::GetAndHandleTransactionResult() {
|
||||
const Result transaction_res = this->GetTransactionResult();
|
||||
R_TRY_CLEANUP(transaction_res, {
|
||||
this->HandleTransactionResult(transaction_res);
|
||||
this->ClearInterruptMask();
|
||||
eventClear(&this->interrupt_event);
|
||||
});
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::WriteTransferHeader(TransferMode transfer_mode, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address, size_t num_bytes) {
|
||||
this->FlushFifos();
|
||||
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, 0x10);
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, static_cast<u32>(num_bytes - 1) & 0xFFF);
|
||||
|
||||
const u32 slave_addr_val = ((transfer_mode == TransferMode_Receive) & 1) | ((slave_address & 0x7F) << 1);
|
||||
u32 hdr_val = 0;
|
||||
hdr_val |= ((this->speed_mode == SpeedMode_HighSpeed) & 1) << 22;
|
||||
hdr_val |= ((transfer_mode == TransferMode_Receive) & 1) << 19;
|
||||
hdr_val |= ((addressing_mode != AddressingMode_7Bit) & 1) << 18;
|
||||
hdr_val |= (1 << 17);
|
||||
hdr_val |= (((option & I2cTransactionOption_Stop) == 0) & 1) << 16;
|
||||
hdr_val |= slave_addr_val;
|
||||
|
||||
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, hdr_val);
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_types.hpp"
|
||||
#include "i2c_registers.hpp"
|
||||
|
||||
class I2cBusAccessor {
|
||||
private:
|
||||
enum TransferMode {
|
||||
TransferMode_Send = 0,
|
||||
TransferMode_Receive = 1,
|
||||
};
|
||||
static constexpr u64 InterruptTimeout = 100'000'000ul;
|
||||
private:
|
||||
Event interrupt_event;
|
||||
HosMutex open_mutex;
|
||||
HosMutex register_mutex;
|
||||
I2cRegisters *i2c_registers = nullptr;
|
||||
ClkRstRegisters clkrst_registers;
|
||||
SpeedMode speed_mode = SpeedMode_Fast;
|
||||
size_t open_sessions = 0;
|
||||
I2cBus bus = I2cBus_I2c1;
|
||||
PcvModule pcv_module = PcvModule_I2C1;
|
||||
bool suspended = false;
|
||||
public:
|
||||
I2cBusAccessor() {
|
||||
/* ... */
|
||||
}
|
||||
private:
|
||||
inline void ClearInterruptMask() const {
|
||||
WriteRegister(&i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0);
|
||||
ReadRegister(&i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0);
|
||||
}
|
||||
|
||||
void SetBus(I2cBus bus);
|
||||
void CreateInterruptEvent(I2cBus bus);
|
||||
void SetClock(SpeedMode speed_mode);
|
||||
|
||||
void ResetController() const;
|
||||
void ClearBus() const;
|
||||
void DisableClock();
|
||||
void SetPacketMode();
|
||||
Result FlushFifos();
|
||||
|
||||
Result GetTransactionResult() const;
|
||||
void HandleTransactionResult(Result result);
|
||||
Result GetAndHandleTransactionResult();
|
||||
|
||||
void WriteTransferHeader(TransferMode transfer_mode, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address, size_t num_bytes);
|
||||
public:
|
||||
void Open(I2cBus bus, SpeedMode speed_mode);
|
||||
void Close();
|
||||
void Suspend();
|
||||
void Resume();
|
||||
void DoInitialConfig();
|
||||
|
||||
size_t GetOpenSessions() const;
|
||||
bool GetBusy() const;
|
||||
|
||||
void OnStartTransaction() const;
|
||||
Result StartTransaction(DriverCommand command, AddressingMode addressing_mode, u32 slave_address);
|
||||
Result Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address);
|
||||
Result Receive(u8 *out_data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address);
|
||||
void OnStopTransaction() const;
|
||||
};
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_command_list.hpp"
|
||||
|
||||
Result I2cCommandListFormatter::CanEnqueue(size_t size) const {
|
||||
if (this->cmd_list_size - this->cur_index < size) {
|
||||
return ResultI2cFullCommandList;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result I2cCommandListFormatter::EnqueueSendCommand(I2cTransactionOption option, const void *src, size_t size) {
|
||||
R_TRY(this->CanEnqueue(SendCommandSize + size));
|
||||
|
||||
this->cmd_list[this->cur_index] = I2cCommand_Send;
|
||||
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Start) != 0) << 6;
|
||||
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Stop) != 0) << 7;
|
||||
this->cur_index++;
|
||||
|
||||
this->cmd_list[this->cur_index++] = size;
|
||||
|
||||
const u8 *src_u8 = reinterpret_cast<const u8 *>(src);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
this->cmd_list[this->cur_index++] = src_u8[i];
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result I2cCommandListFormatter::EnqueueReceiveCommand(I2cTransactionOption option, size_t size) {
|
||||
R_TRY(this->CanEnqueue(ReceiveCommandSize));
|
||||
|
||||
this->cmd_list[this->cur_index] = I2cCommand_Receive;
|
||||
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Start) != 0) << 6;
|
||||
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Stop) != 0) << 7;
|
||||
this->cur_index++;
|
||||
|
||||
this->cmd_list[this->cur_index++] = size;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result I2cCommandListFormatter::EnqueueSleepCommand(size_t us) {
|
||||
R_TRY(this->CanEnqueue(SleepCommandSize));
|
||||
|
||||
this->cmd_list[this->cur_index] = I2cCommand_SubCommand;
|
||||
this->cmd_list[this->cur_index] |= I2cSubCommand_Sleep << 2;
|
||||
this->cur_index++;
|
||||
|
||||
this->cmd_list[this->cur_index++] = us;
|
||||
return ResultSuccess;
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_types.hpp"
|
||||
|
||||
class I2cCommandListFormatter {
|
||||
public:
|
||||
static constexpr size_t MaxCommandListSize = 0x100;
|
||||
static constexpr size_t SendCommandSize = 2;
|
||||
static constexpr size_t ReceiveCommandSize = 2;
|
||||
static constexpr size_t SleepCommandSize = 2;
|
||||
private:
|
||||
u8 *cmd_list = nullptr;
|
||||
size_t cmd_list_size = 0;
|
||||
size_t cur_index = 0;
|
||||
public:
|
||||
I2cCommandListFormatter(void *cmd_list, size_t cmd_list_size) : cmd_list(static_cast<u8 *>(cmd_list)), cmd_list_size(cmd_list_size) {
|
||||
if (cmd_list_size > MaxCommandListSize) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
~I2cCommandListFormatter() {
|
||||
this->cmd_list = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Result CanEnqueue(size_t size) const;
|
||||
public:
|
||||
size_t GetCurrentSize() const {
|
||||
return this->cur_index;
|
||||
}
|
||||
|
||||
Result EnqueueSendCommand(I2cTransactionOption option, const void *src, size_t size);
|
||||
Result EnqueueReceiveCommand(I2cTransactionOption option, size_t size);
|
||||
Result EnqueueSleepCommand(size_t us);
|
||||
};
|
@ -1,117 +0,0 @@
|
||||
/*
|
||||
* 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 <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "i2c_types.hpp"
|
||||
|
||||
struct DeviceConfig {
|
||||
I2cDevice device;
|
||||
I2cBus bus;
|
||||
u32 slave_address;
|
||||
AddressingMode addressing_mode;
|
||||
SpeedMode speed_mode;
|
||||
u32 max_retries;
|
||||
u64 retry_wait_time;
|
||||
};
|
||||
|
||||
static constexpr DeviceConfig g_device_configs[I2cDevice_Count] = {
|
||||
{I2cDevice_DebugPad, I2cBus_I2c1, 0x52, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
|
||||
{I2cDevice_TouchPanel, I2cBus_I2c3, 0x49, AddressingMode_7Bit, SpeedMode_Fast, 0, 0},
|
||||
{I2cDevice_Tmp451, I2cBus_I2c1, 0x4c, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
|
||||
{I2cDevice_Nct72, I2cBus_I2c1, 0x4c, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
|
||||
{I2cDevice_Alc5639, I2cBus_I2c1, 0x1c, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
|
||||
{I2cDevice_Max77620Rtc, I2cBus_I2c5, 0x68, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Max77620Pmic, I2cBus_I2c5, 0x3c, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Max77621Cpu, I2cBus_I2c5, 0x1b, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Max77621Gpu, I2cBus_I2c5, 0x1c, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Bq24193, I2cBus_I2c1, 0x6b, AddressingMode_7Bit, SpeedMode_Normal, 3, 5'000'000},
|
||||
{I2cDevice_Max17050, I2cBus_I2c1, 0x36, AddressingMode_7Bit, SpeedMode_Normal, 3, 5'000'000},
|
||||
{I2cDevice_Bm92t30mwv, I2cBus_I2c1, 0x18, AddressingMode_7Bit, SpeedMode_Normal, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Vdd15v0Hb, I2cBus_I2c2, 0x40, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysCpuDs, I2cBus_I2c2, 0x41, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysGpuDs, I2cBus_I2c2, 0x44, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysDdrDs, I2cBus_I2c2, 0x45, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysAp, I2cBus_I2c2, 0x46, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysBlDs, I2cBus_I2c2, 0x47, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Bh1730, I2cBus_I2c2, 0x29, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226VsysCore, I2cBus_I2c2, 0x48, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Soc1V8, I2cBus_I2c2, 0x49, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Lpddr1V8, I2cBus_I2c2, 0x4a, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Reg1V32, I2cBus_I2c2, 0x4b, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_Ina226Vdd3V3Sys, I2cBus_I2c2, 0x4d, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
{I2cDevice_HdmiDdc, I2cBus_I2c4, 0x50, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
|
||||
{I2cDevice_HdmiScdc, I2cBus_I2c4, 0x54, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
|
||||
{I2cDevice_HdmiHdcp, I2cBus_I2c4, 0x3a, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
|
||||
{I2cDevice_Fan53528, I2cBus_I2c5, 0xa4, AddressingMode_7Bit, SpeedMode_Fast, 0, 0},
|
||||
{I2cDevice_Max77812_3, I2cBus_I2c5, 0x31, AddressingMode_7Bit, SpeedMode_Fast, 0, 0},
|
||||
{I2cDevice_Max77812_2, I2cBus_I2c5, 0x33, AddressingMode_7Bit, SpeedMode_Fast, 0, 0},
|
||||
{I2cDevice_Ina226VddDdr0V6, I2cBus_I2c2, 0x4e, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
|
||||
};
|
||||
|
||||
static constexpr size_t NumDeviceConfigs = sizeof(g_device_configs) / sizeof(g_device_configs[0]);
|
||||
|
||||
static constexpr size_t I2cDeviceInvalidIndex = static_cast<size_t>(-1);
|
||||
|
||||
static size_t GetI2cDeviceIndex(I2cDevice dev) {
|
||||
for (size_t i = 0; i < NumDeviceConfigs; i++) {
|
||||
if (g_device_configs[i].device == dev) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return I2cDeviceInvalidIndex;
|
||||
}
|
||||
|
||||
bool IsI2cDeviceSupported(I2cDevice dev) {
|
||||
return GetI2cDeviceIndex(dev) != I2cDeviceInvalidIndex;
|
||||
}
|
||||
|
||||
I2cBus GetI2cDeviceBus(I2cDevice dev) {
|
||||
const size_t dev_idx = GetI2cDeviceIndex(dev);
|
||||
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].bus;
|
||||
}
|
||||
|
||||
u32 GetI2cDeviceSlaveAddress(I2cDevice dev) {
|
||||
const size_t dev_idx = GetI2cDeviceIndex(dev);
|
||||
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].slave_address;
|
||||
}
|
||||
|
||||
AddressingMode GetI2cDeviceAddressingMode(I2cDevice dev) {
|
||||
const size_t dev_idx = GetI2cDeviceIndex(dev);
|
||||
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].addressing_mode;
|
||||
}
|
||||
|
||||
SpeedMode GetI2cDeviceSpeedMode(I2cDevice dev) {
|
||||
const size_t dev_idx = GetI2cDeviceIndex(dev);
|
||||
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].speed_mode;
|
||||
}
|
||||
|
||||
u32 GetI2cDeviceMaxRetries(I2cDevice dev) {
|
||||
const size_t dev_idx = GetI2cDeviceIndex(dev);
|
||||
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].max_retries;
|
||||
}
|
||||
|
||||
u64 GetI2cDeviceRetryWaitTime(I2cDevice dev) {
|
||||
const size_t dev_idx = GetI2cDeviceIndex(dev);
|
||||
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
|
||||
return g_device_configs[dev_idx].retry_wait_time;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user