sdmmc: complete abstract logic for Sdmmc1 power controller

This commit is contained in:
Michael Scire 2020-10-21 12:26:15 -07:00
parent bd9b01e405
commit 9340183541
9 changed files with 429 additions and 15 deletions

View file

@ -20,4 +20,7 @@ namespace ams::dd {
uintptr_t QueryIoMapping(dd::PhysicalAddress phys_addr, size_t size);
u32 ReadIoRegister(dd::PhysicalAddress phys_addr);
void WriteIoRegister(dd::PhysicalAddress phys_addr, u32 value);
}

View file

@ -41,6 +41,7 @@
#include <vapours/results/nim_results.hpp>
#include <vapours/results/ns_results.hpp>
#include <vapours/results/os_results.hpp>
#include <vapours/results/pcv_results.hpp>
#include <vapours/results/pgl_results.hpp>
#include <vapours/results/pm_results.hpp>
#include <vapours/results/psc_results.hpp>

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours/results/results_common.hpp>
namespace ams::pcv {
R_DEFINE_NAMESPACE_RESULT_MODULE(133);
R_DEFINE_ERROR_RESULT(IllegalRequest, 16);
}

View file

@ -202,6 +202,10 @@ DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_VE2, 29, OFF, ON);
DEFINE_PMC_REG(PWRGATE_STATUS_CE123, 9, 3);
DEFINE_PMC_REG_BIT_ENUM(NO_IOPOWER_SDMMC1, 12, DISABLE, ENABLE);
DEFINE_PMC_REG_BIT_ENUM(PWR_DET_SDMMC1, 12, DISABLE, ENABLE);
DEFINE_PMC_REG_BIT_ENUM(PWR_DET_VAL_SDMMC1, 12, DISABLE, ENABLE);
DEFINE_PMC_REG(SET_SW_CLAMP_CRAIL, 0, 1);
DEFINE_PMC_REG_TWO_BIT_ENUM(IO_DPD_REQ_CODE, 30, IDLE, DPD_OFF, DPD_ON, RESERVED3);

View file

@ -66,4 +66,32 @@ namespace ams::dd {
#endif
}
u32 ReadIoRegister(dd::PhysicalAddress phys_addr) {
#if defined(ATMOSPHERE_IS_EXOSPHERE) || defined(ATMOSPHERE_IS_MESOSPHERE)
return reg::Read(dd::QueryIoMapping(phys_addr, sizeof(u32)));
#elif defined(ATMOSPHERE_IS_STRATOSPHERE)
u32 val;
R_ABORT_UNLESS(svc::ReadWriteRegister(std::addressof(val), phys_addr, 0, 0));
return val;
#else
#error "Unknown execution context for ams::dd::ReadIoRegister!"
#endif
}
void WriteIoRegister(dd::PhysicalAddress phys_addr, u32 value) {
#if defined(ATMOSPHERE_IS_EXOSPHERE) || defined(ATMOSPHERE_IS_MESOSPHERE)
reg::Write(dd::QueryIoMapping(phys_addr, sizeof(u32)), value);
#elif defined(ATMOSPHERE_IS_STRATOSPHERE)
u32 out_val;
R_ABORT_UNLESS(svc::ReadWriteRegister(std::addressof(val), phys_addr, 0xFFFFFFFF, value));
AMS_UNUSED(out_val);
#else
#error "Unknown execution context for ams::dd::WriteIoRegister!"
#endif
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if defined(ATMOSPHERE_IS_STRATOSPHERE)
#include <stratosphere.hpp>
#elif defined(ATMOSPHERE_IS_MESOSPHERE)
#include <mesosphere.hpp>
#elif defined(ATMOSPHERE_IS_EXOSPHERE)
#include <exosphere.hpp>
#else
#include <vapours.hpp>
#endif
#include "sdmmc_io_impl.board.nintendo_nx.hpp"
namespace ams::sdmmc::impl {
Result SetSdCardVoltageEnabled(bool en) {
/* TODO */
}
Result SetSdCardVoltageValue(u32 micro_volts) {
/* TODO */
}
namespace pinmux_impl {
void SetPinAssignment(PinAssignment assignment) {
/* TODO */
}
}
namespace gpio_impl {
void OpenSession(GpioPadName pad) {
/* TODO */
}
void CloseSession(GpioPadName pad) {
/* TODO */
}
void SetDirection(GpioPadName pad, Direction direction) {
/* TODO */
}
void SetValue(GpioPadName pad, GpioValue value) {
/* TODO */
}
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
namespace ams::sdmmc::impl {
Result SetSdCardVoltageEnabled(bool en);
Result SetSdCardVoltageValue(u32 micro_volts);
namespace pinmux_impl {
enum PinAssignment {
PinAssignment_Sdmmc1OutputHigh = 2,
PinAssignment_Sdmmc1ResetState = 3,
PinAssignment_Sdmmc1SchmtEnable = 4,
PinAssignment_Sdmmc1SchmtDisable = 5,
};
void SetPinAssignment(PinAssignment assignment);
}
namespace gpio_impl {
enum GpioValue {
GpioValue_Low = 0,
GpioValue_High = 1
};
enum Direction {
Direction_Input = 0,
Direction_Output = 1,
};
enum GpioPadName {
GpioPadName_PowSdEn = 2,
};
void OpenSession(GpioPadName pad);
void CloseSession(GpioPadName pad);
void SetDirection(GpioPadName pad, Direction direction);
void SetValue(GpioPadName pad, GpioValue value);
}
}

View file

@ -23,6 +23,7 @@
#include <vapours.hpp>
#endif
#include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp"
#include "sdmmc_io_impl.board.nintendo_nx.hpp"
#include "sdmmc_timer.hpp"
namespace ams::sdmmc::impl {
@ -875,7 +876,9 @@ namespace ams::sdmmc::impl {
/* Nintendo sets the current bus power regardless of whether the call succeeds. */
ON_SCOPE_EXIT { this->current_bus_power = BusPower_3_3V; };
/* TODO: equivalent of return pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */
/* pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */
R_TRY(this->power_controller->PowerOn(BusPower_3_3V));
return ResultSuccess();
}
@ -887,23 +890,34 @@ namespace ams::sdmmc::impl {
/* If we're at 3.3V, lower to 1.8V. */
{
/* TODO: equivalent of pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
/* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
this->power_controller->LowerBusPower();
/* Set our bus power. */
this->current_bus_power = BusPower_1_8V;
}
/* TODO: Equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */
/* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */
pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1OutputHigh);
/* TODO: Equivalent of pcv::PowerOff(pcv::PowerControlTarget_SdCard); */
/* pcv::PowerOff(pcv::PowerControlTarget_SdCard); */
this->power_controller->PowerOff();
/* Set our bus power. */
this->current_bus_power = BusPower_Off;
/* TODO: Equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */
/* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */
pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1ResetState);
}
Result Sdmmc1Controller::LowerBusPowerForRegisterControl() {
/* Nintendo sets the current bus power regardless of whether the call succeeds. */
ON_SCOPE_EXIT { this->current_bus_power = BusPower_1_8V; };
/* TODO: equivalent of return pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
/* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
R_TRY(this->power_controller->LowerBusPower());
return ResultSuccess();
}
@ -911,14 +925,17 @@ namespace ams::sdmmc::impl {
SdHostStandardController::EnsureControl();
if (IsSocMariko()) {
/* TODO: equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
/* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable);
} else {
switch (bus_power) {
case BusPower_1_8V:
/* TODO: equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
/* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable);
break;
case BusPower_3_3V:
/* TODO: equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */
/* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */
pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtDisable);
break;
case BusPower_Off:
AMS_UNREACHABLE_DEFAULT_CASE();
@ -1051,9 +1068,17 @@ namespace ams::sdmmc::impl {
this->is_pcv_control = false;
#endif
/* TODO: equivalent of pinmux::Initialize(); */
/* TODO: equivalent of pinmux::OpenSession(std::addressof(this->pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */
/* TODO: equivalent of pcv::Initialize(); */
/* pinmux::Initialize(); */
/* This just opens a session handle to pinmux service, no work to do. */
/* pinmux::OpenSession(std::addressof(this->pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */
/* This just sets the session's internal value to the pin group name, so nothing to do here either. */
/* pcv::Initialize(); */
/* This initializes a lot of globals in pcv, most of which we don't care about. */
/* However, we do care about the Sdmmc1PowerController. */
AMS_ABORT_UNLESS(this->power_controller == nullptr);
this->power_controller = new (GetPointer(this->power_controller_storage)) PowerController;
/* Perform base initialization. */
SdmmcController::Initialize();
@ -1063,9 +1088,18 @@ namespace ams::sdmmc::impl {
/* Perform base finalization. */
SdmmcController::Finalize();
/* TODO: equivalent of pcv::Finalize(); */
/* TODO: equivalent of pinmux::CloseSession(std::addressof(this->pinmux_session)); */
/* TODO: equivalent of pinmux::Finalize(); */
/* pcv::Finalize(); */
/* As with initialize, we mostly don't care about the globals this touches. */
/* However, we do want to finalize the Sdmmc1PowerController. */
AMS_ABORT_UNLESS(this->power_controller != nullptr);
this->power_controller->~PowerController();
this->power_controller = nullptr;
/* pinmux::CloseSession(std::addressof(this->pinmux_session)); */
/* This does nothing. */
/* pinmux::Finalize(); */
/* This does nothing. */
#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
/* Mark ourselves as initialized by register control. */
@ -1099,4 +1133,172 @@ namespace ams::sdmmc::impl {
}
#endif
namespace {
constexpr inline dd::PhysicalAddress PmcRegistersPhysicalAddress = 0x7000E400;
}
Result Sdmmc1Controller::PowerController::ControlVddioSdmmc1(BusPower bus_power) {
/* Configure appropriate voltage. */
switch (bus_power) {
case BusPower_Off:
R_TRY(SetSdCardVoltageEnabled(false));
break;
case BusPower_1_8V:
R_TRY(SetSdCardVoltageValue(1'800'000));
R_TRY(SetSdCardVoltageEnabled(true));
break;
case BusPower_3_3V:
R_TRY(SetSdCardVoltageValue(3'300'000));
R_TRY(SetSdCardVoltageEnabled(true));
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
return ResultSuccess();
}
void Sdmmc1Controller::PowerController::SetSdmmcIoMode(bool is_3_3V) {
/* Determine the address we're updating. */
constexpr dd::PhysicalAddress ApbdevPmcPwrDetValAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_PWR_DET_VAL;
/* Read the current value. */
u32 value = dd::ReadIoRegister(ApbdevPmcPwrDetValAddress);
/* Mask out the existing bits. */
value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(PWR_DET_VAL_SDMMC1)));
/* ORR in the new bits. */
value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(PWR_DET_VAL_SDMMC1, is_3_3V, ENABLE, DISABLE));
/* Write the new value. */
dd::WriteIoRegister(ApbdevPmcPwrDetValAddress, value);
/* Read the value back to be sure our write takes. */
dd::ReadIoRegister(ApbdevPmcPwrDetValAddress);
}
void Sdmmc1Controller::PowerController::ControlRailSdmmc1Io(bool is_power_on) {
/* Determine the address we're updating. */
constexpr dd::PhysicalAddress ApbdevPmcNoIoPowerAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_NO_IOPOWER;
/* Read the current value. */
u32 value = dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress);
/* Mask out the existing bits. */
value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(NO_IOPOWER_SDMMC1)));
/* ORR in the new bits. */
value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(NO_IOPOWER_SDMMC1, is_power_on, DISABLE, ENABLE));
/* Write the new value. */
dd::WriteIoRegister(ApbdevPmcNoIoPowerAddress, value);
/* Read the value back to be sure our write takes. */
dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress);
}
Sdmmc1Controller::PowerController::PowerController() : current_bus_power(BusPower_Off) {
/* gpio::Initialize(); */
/* ... */
/* Open gpio session. */
/* gpio::OpenSession(std::addressof(this->gpio_pad_session), gpio::GpioPadName_PowSdEn); */
gpio_impl::OpenSession(gpio_impl::GpioPadName_PowSdEn);
/* Configure the gpio as low/output. */
/* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_Low); */
gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low);
/* gpio::SetDirection(std::addressof(this->gpio_pad_session), gpio::Direction_Output); */
gpio_impl::SetDirection(gpio_impl::GpioPadName_PowSdEn, gpio_impl::Direction_Output);
}
Sdmmc1Controller::PowerController::~PowerController() {
/* gpio::CloseSession(std::addressof(this->gpio_pad_session)); */
gpio_impl::CloseSession(gpio_impl::GpioPadName_PowSdEn);
/* gpio::Finalize(); */
/* ... */
}
Result Sdmmc1Controller::PowerController::PowerOn(BusPower bus_power) {
/* Bus power should be off, and if it's not we don't need to do anything. */
AMS_ASSERT(this->current_bus_power == BusPower_Off);
R_SUCCEED_IF(this->current_bus_power != BusPower_Off);
/* Power on requires the target bus power be 3.3V. */
AMS_ABORT_UNLESS(bus_power == BusPower_3_3V);
/* Enable the rail. */
this->ControlRailSdmmc1Io(true);
/* Set the SD power GPIO to high. */
/* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_High); */
gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_High);
/* Wait 10ms for power change to take. */
WaitMicroSeconds(10000);
/* Configure Sdmmc1 IO as 3.3V. */
this->SetSdmmcIoMode(true);
R_TRY(this->ControlVddioSdmmc1(BusPower_3_3V));
/* Wait 130 us for changes to take. */
WaitMicroSeconds(130);
/* Update our current bus power. */
this->current_bus_power = bus_power;
return ResultSuccess();
}
Result Sdmmc1Controller::PowerController::PowerOff() {
/* Bus power should be on, and if it's not we don't need to do anything. */
AMS_ASSERT(this->current_bus_power != BusPower_Off);
R_SUCCEED_IF(this->current_bus_power == BusPower_Off);
/* Bus power should be 1.8V. */
/* NOTE: the result returned here is 0x8C0 (regulator::ResultIllegalRequest()) on newer firmwares. */
AMS_ASSERT(this->current_bus_power == BusPower_1_8V);
R_UNLESS(this->current_bus_power == BusPower_1_8V, pcv::ResultIllegalRequest());
/* Disable vddio, and wait 4 ms. */
this->ControlVddioSdmmc1(BusPower_Off);
WaitMicroSeconds(4000);
/* Set the SD power GPIO to low. */
/* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_Low); */
gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low);
/* Wait 239ms for the gpio config to take. */
WaitMicroSeconds(239000);
/* Disable the rail. */
this->ControlRailSdmmc1Io(false);
this->SetSdmmcIoMode(true);
/* Update our current bus power. */
this->current_bus_power = BusPower_Off;
return ResultSuccess();
}
Result Sdmmc1Controller::PowerController::LowerBusPower() {
/* Bus power should be 3.3V, and if it's not we don't need to do anything. */
AMS_ASSERT(this->current_bus_power == BusPower_3_3V);
R_SUCCEED_IF(this->current_bus_power != BusPower_3_3V);
/* Configure as 1.8V, then wait 150us for it to take. */
R_TRY(this->ControlVddioSdmmc1(BusPower_1_8V));
WaitMicroSeconds(150);
this->SetSdmmcIoMode(false);
/* Update our current bus power. */
this->current_bus_power = BusPower_1_8V;
return ResultSuccess();
}
}

View file

@ -210,6 +210,25 @@ namespace ams::sdmmc::impl {
#if defined(AMS_SDMMC_USE_OS_EVENTS)
static constinit inline os::InterruptEventType s_interrupt_event{};
#endif
/* NOTE: This is a fascimile of pcv's Sdmmc1PowerController. */
class PowerController {
NON_COPYABLE(PowerController);
NON_MOVEABLE(PowerController);
private:
BusPower current_bus_power;
private:
Result ControlVddioSdmmc1(BusPower bus_power);
void SetSdmmcIoMode(bool is_3_3V);
void ControlRailSdmmc1Io(bool is_power_on);
public:
PowerController();
~PowerController();
Result PowerOn(BusPower bus_power);
Result PowerOff();
Result LowerBusPower();
};
private:
#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
/* TODO: pinmux::PinmuxSession pinmux_session; */
@ -218,6 +237,8 @@ namespace ams::sdmmc::impl {
#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
bool is_pcv_control;
#endif
TYPED_STORAGE(PowerController) power_controller_storage;
PowerController *power_controller;
private:
Result PowerOnForRegisterControl(BusPower bus_power);
void PowerOffForRegisterControl();
@ -381,6 +402,7 @@ namespace ams::sdmmc::impl {
#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
this->is_pcv_control = false;
#endif
this->power_controller = nullptr;
}
virtual void Initialize() override;