sdmmc: implement clock reset controller for register api

This commit is contained in:
Michael Scire 2020-10-25 19:08:19 -07:00
parent 397d0c4295
commit 37704d670b
10 changed files with 748 additions and 12 deletions

View file

@ -51,6 +51,11 @@ namespace ams::reg {
return (EncodeValue(values) | ...);
}
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
constexpr ALWAYS_INLINE u32 EncodeMask(const Masks... masks) {
return (EncodeMask(masks) | ...);
}
template<typename IntType> requires UnsignedNonConstIntegral<IntType>
ALWAYS_INLINE void Write(volatile IntType *reg, std::type_identity_t<IntType> val) { *reg = val; }

View file

@ -33,6 +33,7 @@
//#define AMS_SDMMC_USE_OS_TIMER
#define AMS_SDMMC_USE_UTIL_TIMER
//#define AMS_SDMMC_ENABLE_MMC_HS400
//#define AMS_SDMMC_SET_PLLC4_BASE
#elif defined(ATMOSPHERE_IS_MESOSPHERE)
@ -45,6 +46,7 @@
//#define AMS_SDMMC_USE_OS_TIMER
#define AMS_SDMMC_USE_UTIL_TIMER
//#define AMS_SDMMC_ENABLE_MMC_HS400
//#define AMS_SDMMC_SET_PLLC4_BASE
#elif defined(ATMOSPHERE_IS_STRATOSPHERE)
@ -57,6 +59,7 @@
#define AMS_SDMMC_USE_OS_TIMER
//#define AMS_SDMMC_USE_UTIL_TIMER
#define AMS_SDMMC_ENABLE_MMC_HS400
#define AMS_SDMMC_SET_PLLC4_BASE
#else
#error "Unknown execution context for ams::sdmmc!"

View file

@ -45,6 +45,7 @@
#define CLK_RST_CONTROLLER_SUPER_CCLKLP_DIVIDER (0x374)
#define CLK_RST_CONTROLLER_CPU_SOFTRST_CTRL2 (0x388)
#define CLK_RST_CONTROLLER_SPARE_REG0 (0x55C)
#define CLK_RST_CONTROLLER_PLLC4_BASE (0x5A4)
#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA (0x0F8)
#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB (0x0FC)
@ -79,6 +80,10 @@ DEFINE_CLK_RST_REG(CPU_SOFTRST_CTRL2_CAR2PMC_CPU_ACK_WIDTH, 0, 12);
DEFINE_CLK_RST_REG_TWO_BIT_ENUM(SPARE_REG0_CLK_M_DIVISOR, 2, CLK_M_DIVISOR1, CLK_M_DIVISOR2, CLK_M_DIVISOR3, CLK_M_DIVISOR4);
DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_IDDQ, 18, OFF, ON);
DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_LOCK, 27, NOT_LOCK, LOCK_FEQ_AND_PHASE);
DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_ENABLE, 30, DISABLE, ENABLE);
/* RST_DEVICES */
#define CLK_RST_CONTROLLER_RST_DEVICES_L (0x004)
#define CLK_RST_CONTROLLER_RST_DEVICES_H (0x008)
@ -98,16 +103,21 @@ DEFINE_CLK_RST_REG_TWO_BIT_ENUM(SPARE_REG0_CLK_M_DIVISOR, 2, CLK_M_DIVISOR1, CLK
#define CLK_RST_CONTROLLER_CLK_OUT_ENB_W (0x364)
/* CLK_SOURCE */
#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C1 (0x124)
#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C5 (0x128)
#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTA (0x178)
#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTB (0x17C)
#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTC (0x1A0)
#define CLK_RST_CONTROLLER_CLK_SOURCE_CSITE (0x1D4)
#define CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT (0x3B4)
#define CLK_RST_CONTROLLER_CLK_SOURCE_ACTMON (0x3E8)
#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_REF (0x62C)
#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_SOC (0x630)
#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C1 (0x124)
#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C5 (0x128)
#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1 (0x150)
#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2 (0x154)
#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4 (0x164)
#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTA (0x178)
#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTB (0x17C)
#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTC (0x1A0)
#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3 (0x1BC)
#define CLK_RST_CONTROLLER_CLK_SOURCE_CSITE (0x1D4)
#define CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT (0x3B4)
#define CLK_RST_CONTROLLER_CLK_SOURCE_ACTMON (0x3E8)
#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_REF (0x62C)
#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_SOC (0x630)
#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM (0x694)
/* RST_DEV_*_SET */
#define CLK_RST_CONTROLLER_RST_DEV_L_SET (0x300)
@ -169,6 +179,19 @@ DEFINE_CLK_RST_REG(CLK_SOURCE_I2C1_I2C1_CLK_DIVISOR, 0, 8);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_I2C5_I2C5_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2);
DEFINE_CLK_RST_REG(CLK_SOURCE_I2C5_I2C5_CLK_DIVISOR, 0, 8);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC1_SDMMC1_CLK_SRC, 29, PLLP_OUT0, PLLA_OUT, PLLC_OUT0, PLLC4_OUT2, PLLM_OUT0, PLLE_OUT0, CLK_M, PLLC4_OUT0);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC2_SDMMC2_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC4_SDMMC4_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC3_SDMMC3_CLK_SRC, 29, PLLP_OUT0, PLLA_OUT, PLLC_OUT0, PLLC4_OUT2, PLLC4_OUT1, PLLE_OUT0, CLK_M, PLLC4_OUT0);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, 29, PLLP_OUT0, _RSVD1_, _RSVD2_, PLLC4_OUT2, _RSVD4_, _RSVD5_, CLK_M, PLLC4_OUT0);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC24_SDMMC24_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0);
DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC1_SDMMC1_CLK_DIVISOR, 0, 8);
DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC2_SDMMC2_CLK_DIVISOR, 0, 8);
DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC4_SDMMC4_CLK_DIVISOR, 0, 8);
DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC3_SDMMC3_CLK_DIVISOR, 0, 8);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTA_UARTA_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTB_UARTB_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTC_UARTC_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2);
@ -184,6 +207,9 @@ DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_DVFS_REF_DVFS_REF_CLK_SRC, 29, PLLP
DEFINE_CLK_RST_REG(CLK_SOURCE_DVFS_SOC_DVFS_SOC_DIVISOR, 0, 8);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_DVFS_SOC_DVFS_SOC_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2);
DEFINE_CLK_RST_REG(CLK_SOURCE_LEGACY_TM_CLK_DIVISOR, 0, 8);
DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_LEGACY_TM_CLK_SRC, 29, PLLP_OUT3, PLLC_OUT0, PLLC2_OUT0, CLK_M, PLLP_OUT0, PLLC4_OUT0, PLLC4_OUT1, PLLC4_OUT2);
DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_L_SET_SET_COP_RST, 1, DISABLE, ENABLE);
DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_L_CLR_CLR_COP_RST, 1, DISABLE, ENABLE);
@ -204,6 +230,9 @@ DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, 29, DISABLE, ENA
HANDLER(L, RTC, 0, 4) \
HANDLER(L, TMR, 0, 5) \
HANDLER(L, GPIO, 0, 8) \
HANDLER(L, SDMMC2, 0, 9) \
HANDLER(L, SDMMC1, 0, 14) \
HANDLER(L, SDMMC4, 0, 15) \
HANDLER(L, USBD, 0, 22) \
HANDLER(L, CACHE2, 0, 31) \
HANDLER(H, MEM, 1, 0) \
@ -215,6 +244,7 @@ DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, 29, DISABLE, ENA
HANDLER(H, I2C5, 1, 15) \
HANDLER(H, EMC, 1, 25) \
HANDLER(H, USB2, 1, 26) \
HANDLER(U, SDMMC3, 2, 5) \
HANDLER(U, CSITE, 2, 9) \
HANDLER(U, IRAMA, 2, 20) \
HANDLER(U, IRAMB, 2, 21) \
@ -244,6 +274,7 @@ DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, 29, DISABLE, ENA
HANDLER(X, GPU, 5, 24) \
HANDLER(X, DBGAPB, 5, 25) \
HANDLER(X, PLLG_REF, 5, 29) \
HANDLER(Y, LEGACY_TM, 6, 1) \
HANDLER(Y, MC_CCPA, 6, 8) \
HANDLER(Y, MC_CDPA, 6, 9) \
HANDLER(Y, PLLP_OUT_CPU, 6, 31)

View file

@ -0,0 +1,151 @@
/*
* 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_clock_reset_controller.hpp"
#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp"
#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp"
namespace ams::sdmmc::impl::ClockResetController {
namespace {
constinit bool g_is_module_initialized[Module_Count] = {};
#if defined(AMS_SDMMC_THREAD_SAFE)
constinit os::SdkMutex g_module_mutex;
#define AMS_SDMMC_LOCK_MODULE_MUTEX() std::scoped_lock lk(g_module_mutex)
#else
#define AMS_SDMMC_LOCK_MODULE_MUTEX()
#endif
#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
constinit bool g_is_pcv_control = false;
#define AMS_SDMMC_IF_IS_PCV_CONTROL() if (g_is_pcv_control)
#else
#define AMS_SDMMC_IF_IS_PCV_CONTROL() if constexpr (false)
#endif
}
void Initialize(Module module) {
/* Acquire exclusive access to module state. */
AMS_SDMMC_LOCK_MODULE_MUTEX();
/* Mark the module as initialized. */
g_is_module_initialized[module] = true;
/* Initialize the module. */
AMS_SDMMC_IF_IS_PCV_CONTROL() {
ClockResetController::pcv::Initialize(module);
} else {
ClockResetController::reg::Initialize(module);
}
}
void Finalize(Module module) {
/* Acquire exclusive access to module state. */
AMS_SDMMC_LOCK_MODULE_MUTEX();
/* Finalize the module. */
AMS_SDMMC_IF_IS_PCV_CONTROL() {
ClockResetController::pcv::Finalize(module);
} else {
ClockResetController::reg::Finalize(module);
}
/* Mark the module as finalized. */
g_is_module_initialized[module] = false;
}
bool IsAvailable(Module module) {
AMS_SDMMC_IF_IS_PCV_CONTROL() {
return ClockResetController::pcv::IsAvailable(module);
} else {
return ClockResetController::reg::IsAvailable(module);
}
}
#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
void SwitchToPcvControl() {
/* Acquire exclusive access to module state. */
AMS_SDMMC_LOCK_MODULE_MUTEX();
/* If we're already using pcv control, we don't need to do anything. */
AMS_SDMMC_IF_IS_PCV_CONTROL() {
return;
}
/* Finalize all modules. */
for (int i = 0; i < Module_Count; ++i) {
if (g_is_module_initialized[i]) {
ClockResetController::reg::Finalize(static_cast<Module>(i));
}
}
/* Mark that we've switched to pcv control. */
/* Initialize modules using pcv control. */
for (int i = 0; i < Module_Count; ++i) {
if (g_is_module_initialized[i]) {
ClockResetController::pcv::Initialize(static_cast<Module>(i));
}
}
}
#endif
void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) {
AMS_SDMMC_IF_IS_PCV_CONTROL() {
return ClockResetController::pcv::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency);
} else {
return ClockResetController::reg::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency);
}
}
void AssertReset(Module module) {
AMS_SDMMC_IF_IS_PCV_CONTROL() {
return ClockResetController::pcv::AssertReset(module);
} else {
return ClockResetController::reg::AssertReset(module);
}
}
void ReleaseReset(Module module, u32 target_frequency_khz) {
AMS_SDMMC_IF_IS_PCV_CONTROL() {
return ClockResetController::pcv::ReleaseReset(module, target_frequency_khz);
} else {
return ClockResetController::reg::ReleaseReset(module, target_frequency_khz);
}
}
}

View file

@ -0,0 +1,95 @@
/*
* 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_clock_reset_controller.pcv.board.nintendo_nx.hpp"
namespace ams::sdmmc::impl::ClockResetController::pcv {
#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
void Initialize(Module module) {
AMS_UNUSED(module);
AMS_ABORT("PCV Control not implemented");
}
void Finalize(Module module) {
AMS_UNUSED(module);
AMS_ABORT("PCV Control not implemented");
}
bool IsAvailable(Module module) {
AMS_UNUSED(module);
AMS_ABORT("PCV Control not implemented");
}
void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) {
AMS_UNUSED(out_actual_frequency, module, target_frequency);
AMS_ABORT("PCV Control not implemented");
}
void AssertReset(Module module) {
AMS_UNUSED(module);
AMS_ABORT("PCV Control not implemented");
}
void ReleaseReset(Module module, u32 target_frequency_khz) {
AMS_UNUSED(module, target_frequency_khz);
AMS_ABORT("PCV Control not implemented");
}
#else
void Initialize(Module module) {
AMS_UNUSED(module);
AMS_ABORT("PCV Control not supported");
}
void Finalize(Module module) {
AMS_UNUSED(module);
AMS_ABORT("PCV Control not supported");
}
bool IsAvailable(Module module) {
AMS_UNUSED(module);
AMS_ABORT("PCV Control not supported");
}
void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) {
AMS_UNUSED(out_actual_frequency, module, target_frequency);
AMS_ABORT("PCV Control not supported");
}
void AssertReset(Module module) {
AMS_UNUSED(module);
AMS_ABORT("PCV Control not supported");
}
void ReleaseReset(Module module, u32 target_frequency_khz) {
AMS_UNUSED(module, target_frequency_khz);
AMS_ABORT("PCV Control not supported");
}
#endif
}

View file

@ -0,0 +1,30 @@
/*
* 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>
#include "sdmmc_clock_reset_controller.hpp"
namespace ams::sdmmc::impl::ClockResetController::pcv {
void Initialize(Module module);
void Finalize(Module module);
bool IsAvailable(Module module);
void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency);
void AssertReset(Module module);
void ReleaseReset(Module module, u32 target_frequency_khz);
}

View file

@ -0,0 +1,391 @@
/*
* 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_clock_reset_controller.reg.board.nintendo_nx.hpp"
#include "sdmmc_timer.hpp"
namespace ams::sdmmc::impl::ClockResetController::reg {
namespace {
#if defined(AMS_SDMMC_THREAD_SAFE)
constinit os::SdkMutex g_init_mutex;
#define AMS_SDMMC_LOCK_INIT_MUTEX() std::scoped_lock lk(g_init_mutex)
#else
#define AMS_SDMMC_LOCK_INIT_MUTEX()
#endif
constinit bool g_is_initialized = false;
struct ModuleInfo {
u32 target_frequency_khz;
u32 actual_frequency_khz;
};
constinit ModuleInfo g_module_infos[Module_Count] = {};
constexpr inline dd::PhysicalAddress ClockResetControllerRegistersPhysicalAddress = UINT64_C(0x60006000);
constexpr inline size_t ClockResetControllerRegistersSize = 4_KB;
constinit uintptr_t g_clkrst_registers_address = 0;
[[maybe_unused]] void InitializePllc4() {
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Check if PLLC4_BASE has the expected value; if it does, we have nothing to do. */
constexpr u32 ExpectedPllc4Base = 0x58006804;
if (ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE) == ExpectedPllc4Base) {
return;
}
/* Disable PLLC4_ENABLE, if it's currently set. */
if (ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE))) {
ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, DISABLE));
}
/* Operate on the register with PLLC4_ENABLE cleared. */
{
/* Clear IDDQ, read to be sure it takes, wait 5 us. */
ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_IDDQ, OFF));
ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE);
WaitMicroSeconds(5);
/* Write the expected value sans IDDQ/PLLC4_ENABLE. */
constexpr u32 ExpectedPllc4BaseMask = ~ams::reg::EncodeMask(CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_ENABLE),
CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_IDDQ));
ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, ExpectedPllc4Base & ExpectedPllc4BaseMask);
}
/* Write PLLC4_ENABLE, and read to be sure our configuration takes. */
ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE));
ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE);
/* Wait up to 1s for changes to take. */
{
ManualTimer timer(1000);
while (true) {
/* Check if we're done. */
if (!ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_LOCK, NOT_LOCK))) {
break;
}
/* Check that we haven't timed out. */
AMS_ABORT_UNLESS(timer.Update());
}
}
}
void InitializeLegacyTmClk() {
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Configure the legacy tm clock as 12MHz. */
ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_LEGACY_TM_CLK_SRC, PLLP_OUT0),
CLK_RST_REG_BITS_VALUE(CLK_SOURCE_LEGACY_TM_CLK_DIVISOR, 66));
/* Enable clock to the legacy tm. */
ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_Y_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_CLK_ENB_LEGACY_TM, ENABLE));
}
bool IsResetReleased(Module module) {
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Get the reset bit from RST_DEVICES_* */
switch (module) {
case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC1_RST, DISABLE));
case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC2_RST, DISABLE));
case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U, CLK_RST_REG_BITS_ENUM(RST_DEVICES_U_SWR_SDMMC3_RST, DISABLE));
case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC4_RST, DISABLE));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void SetReset(Module module) {
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Set reset in RST_DEV_*_SET */
switch (module) {
case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE));
case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE));
case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE));
case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void ClearReset(Module module) {
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Set reset in RST_DEV_*_CLR */
switch (module) {
case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE));
case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE));
case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE));
case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
bool IsClockEnabled(Module module) {
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Get the enable bit from CLK_OUT_ENB_* */
switch (module) {
case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC1, ENABLE));
case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC2, ENABLE));
case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_SDMMC3, ENABLE));
case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC4, ENABLE));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void SetClockEnable(Module module) {
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Set clock enable bit in CLK_ENB_*_SET */
switch (module) {
case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE));
case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE));
case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE));
case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void ClearClockEnable(Module module) {
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Set clock enable bit in CLK_ENB_*_CLR */
switch (module) {
case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE));
case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE));
case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE));
case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void SetClockSourceSdmmc(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) {
/* Check that we can write to output. */
AMS_ABORT_UNLESS(out_actual_frequency_khz != nullptr);
/* Determine frequency/divisor. */
u32 clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLP_OUT0));
u8 n;
switch (target_frequency_khz) {
case 25'000:
*out_actual_frequency_khz = 24'728;
n = 31;
break;
case 26'000:
*out_actual_frequency_khz = 25'500;
n = 30;
break;
case 40'800:
*out_actual_frequency_khz = 40'800;
n = 18;
break;
case 50'000:
*out_actual_frequency_khz = 48'000;
n = 15;
break;
case 52'000:
*out_actual_frequency_khz = 51'000;
n = 14;
break;
case 100'000:
#if defined(AMS_SDMMC_SET_PLLC4_BASE)
*out_actual_frequency_khz = 99'840;
n = 2;
clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2));
#else
*out_actual_frequency_khz = 90'667;
n = 7;
#endif
break;
case 200'000:
#if defined(AMS_SDMMC_SET_PLLC4_BASE)
*out_actual_frequency_khz = 199'680;
n = 0;
if (module == Module_Sdmmc2 || module == Module_Sdmmc4) {
clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMC24_SDMMC24_CLK_SRC, PLLC4_OUT2_LJ));
} else {
clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2));
}
#else
*out_actual_frequency_khz = 163'200;
n = 3;
#endif
break;
case 208'000:
*out_actual_frequency_khz = 204'000;
n = 2;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
/* Set frequencies in module info. */
g_module_infos[module].target_frequency_khz = target_frequency_khz;
g_module_infos[module].actual_frequency_khz = *out_actual_frequency_khz;
/* Check that we have registers we can write to. */
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Update the clock source. */
switch (module) {
case Module_Sdmmc1: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1, clk_m | static_cast<u32>(n)); break;
case Module_Sdmmc2: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2, clk_m | static_cast<u32>(n)); break;
case Module_Sdmmc3: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3, clk_m | static_cast<u32>(n)); break;
case Module_Sdmmc4: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4, clk_m | static_cast<u32>(n)); break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
void EnsureControl(Module module) {
/* Read from RST_DEVICES_* to be sure previous configuration takes. */
switch (module) {
case Module_Sdmmc1: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break;
case Module_Sdmmc2: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break;
case Module_Sdmmc3: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U); break;
case Module_Sdmmc4: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
}
void Initialize(Module module) {
/* Initialization isn't module specific. */
AMS_UNUSED(module);
/* Acquire exclusive access to the initialization lock. */
AMS_SDMMC_LOCK_INIT_MUTEX();
/* If we've already initialized, we don't need to do anything. */
if (g_is_initialized) {
return;
}
/* Clear module infos. */
std::memset(g_module_infos, 0, sizeof(g_module_infos));
/* Get the registers address. */
g_clkrst_registers_address = dd::QueryIoMapping(ClockResetControllerRegistersPhysicalAddress, ClockResetControllerRegistersSize);
AMS_ABORT_UNLESS(g_clkrst_registers_address != 0);
/* Perform register initialization. */
#if defined(AMS_SDMMC_SET_PLLC4_BASE)
InitializePllc4();
#endif
InitializeLegacyTmClk();
/* Mark that we've initialized. */
g_is_initialized = true;
}
void Finalize(Module module) {
/* Nothing is needed for finalization. */
AMS_UNUSED(module);
}
bool IsAvailable(Module module) {
return IsResetReleased(module) && IsClockEnabled(module);
}
void SetClockFrequencyKHz(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) {
/* If we're not changing the clock frequency, we don't need to do anything. */
if (target_frequency_khz == g_module_infos[module].target_frequency_khz) {
*out_actual_frequency_khz = g_module_infos[module].actual_frequency_khz;
return;
}
/* Temporarily disable clock. */
const bool clock_enabled = IsClockEnabled(module);
if (clock_enabled) {
ClearClockEnable(module);
}
/* Set the clock source. */
SetClockSourceSdmmc(out_actual_frequency_khz, module, target_frequency_khz);
/* Re-enable clock, if we should. */
if (clock_enabled) {
SetClockEnable(module);
}
/* Ensure that our configuration takes. */
EnsureControl(module);
}
void AssertReset(Module module) {
/* Set reset and disable clock. */
SetReset(module);
ClearClockEnable(module);
/* Ensure that our configuration takes. */
EnsureControl(module);
}
void ReleaseReset(Module module, u32 target_frequency_khz) {
/* Disable clock if it's enabled. */
if (IsClockEnabled(module)) {
ClearClockEnable(module);
}
/* Set reset. */
SetReset(module);
/* Set the clock source. */
u32 actual_source_frequency_khz;
SetClockSourceSdmmc(std::addressof(actual_source_frequency_khz), module, target_frequency_khz);
/* Enable clock. */
SetClockEnable(module);
/* Ensure that our configuration takes. */
EnsureControl(module);
/* Wait 100 clocks. */
WaitClocks(100, actual_source_frequency_khz);
/* Clear reset. */
ClearReset(module);
/* Ensure that our configuration takes. */
EnsureControl(module);
}
}

View file

@ -0,0 +1,30 @@
/*
* 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>
#include "sdmmc_clock_reset_controller.hpp"
namespace ams::sdmmc::impl::ClockResetController::reg {
void Initialize(Module module);
void Finalize(Module module);
bool IsAvailable(Module module);
void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency);
void AssertReset(Module module);
void ReleaseReset(Module module, u32 target_frequency_khz);
}

View file

@ -300,7 +300,7 @@ namespace ams::sdmmc::impl {
namespace {
#if defined(AMS_SDMMC_THREAD_SAFE)
constinit os::Mutex g_i2c_init_mutex(false);
constinit os::SdkMutex g_i2c_init_mutex;
#define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() std::scoped_lock lk(g_i2c_init_mutex)

View file

@ -171,7 +171,7 @@ namespace ams::sdmmc::impl {
namespace {
#if defined(AMS_SDMMC_THREAD_SAFE)
constinit os::Mutex g_soc_mutex(false);
constinit os::SdkMutex g_soc_mutex;
#define AMS_SDMMC_LOCK_SOC_MUTEX() std::scoped_lock lk(g_soc_mutex)