diff --git a/libraries/libexosphere/include/exosphere.hpp b/libraries/libexosphere/include/exosphere.hpp index d7403cbf5..140d96163 100644 --- a/libraries/libexosphere/include/exosphere.hpp +++ b/libraries/libexosphere/include/exosphere.hpp @@ -17,7 +17,6 @@ #include #include -#include #include #include #include diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_mc.hpp b/libraries/libexosphere/include/exosphere/tegra/tegra_mc.hpp index 107bf548d..5840b9d45 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_mc.hpp +++ b/libraries/libexosphere/include/exosphere/tegra/tegra_mc.hpp @@ -15,7 +15,6 @@ */ #pragma once #include -#include #define MC_INTSTATUS (0x000) #define MC_INTMASK (0x004) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_pmc.hpp b/libraries/libexosphere/include/exosphere/tegra/tegra_pmc.hpp index ecd8d1860..28c0e298b 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_pmc.hpp +++ b/libraries/libexosphere/include/exosphere/tegra/tegra_pmc.hpp @@ -15,7 +15,6 @@ */ #pragma once #include -#include #define APBDEV_PMC_CNTRL (0x000) #define APBDEV_PMC_WAKE_MASK (0x00C) diff --git a/libraries/libexosphere/include/exosphere/util.hpp b/libraries/libexosphere/include/exosphere/util.hpp index 088033e7c..ce31b63ea 100644 --- a/libraries/libexosphere/include/exosphere/util.hpp +++ b/libraries/libexosphere/include/exosphere/util.hpp @@ -20,14 +20,6 @@ namespace ams::util { void SetRegisterAddress(uintptr_t address); - u32 GetMicroSeconds(); - void WaitMicroSeconds(int us); - void ClearMemory(void *ptr, size_t size); - template requires std::integral && std::integral - constexpr T DivideUp(T x, U y) { - return (x + (y - 1)) / y; - } - } \ No newline at end of file diff --git a/libraries/libstratosphere/include/stratosphere.hpp b/libraries/libstratosphere/include/stratosphere.hpp index 9fffefa9f..c46b4dd08 100644 --- a/libraries/libstratosphere/include/stratosphere.hpp +++ b/libraries/libstratosphere/include/stratosphere.hpp @@ -60,7 +60,6 @@ #include #include #include -#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp b/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp index 4cd493b28..c0f3ac992 100644 --- a/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp +++ b/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp @@ -19,16 +19,13 @@ namespace ams::dd { - uintptr_t QueryIoMapping(uintptr_t phys_addr, size_t size); - - u32 ReadRegister(uintptr_t phys_addr); - void WriteRegister(uintptr_t phys_addr, u32 value); - u32 ReadWriteRegister(uintptr_t phys_addr, u32 value, u32 mask); + u32 ReadRegister(dd::PhysicalAddress phys_addr); + void WriteRegister(dd::PhysicalAddress phys_addr, u32 value); + u32 ReadWriteRegister(dd::PhysicalAddress phys_addr, u32 value, u32 mask); /* Convenience Helper. */ - - inline uintptr_t GetIoMapping(uintptr_t phys_addr, size_t size) { - const uintptr_t io_mapping = QueryIoMapping(phys_addr, size); + inline uintptr_t GetIoMapping(dd::PhysicalAddress phys_addr, size_t size) { + const uintptr_t io_mapping = dd::QueryIoMapping(phys_addr, size); AMS_ABORT_UNLESS(io_mapping); return io_mapping; } diff --git a/libraries/libstratosphere/include/stratosphere/reg.hpp b/libraries/libstratosphere/include/stratosphere/reg.hpp deleted file mode 100644 index 6b5076f93..000000000 --- a/libraries/libstratosphere/include/stratosphere/reg.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include - -namespace ams::reg { - - inline void Write(volatile u32 *reg, u32 val) { - *reg = val; - } - - inline void Write(uintptr_t reg, u32 val) { - Write(reinterpret_cast(reg), val); - } - - inline u32 Read(volatile u32 *reg) { - return *reg; - } - - inline u32 Read(uintptr_t reg) { - return Read(reinterpret_cast(reg)); - } - - inline void SetBits(volatile u32 *reg, u32 mask) { - *reg = *reg | mask; - } - - inline void SetBits(uintptr_t reg, u32 mask) { - SetBits(reinterpret_cast(reg), mask); - } - - inline void ClearBits(volatile u32 *reg, u32 mask) { - *reg = *reg & ~mask; - } - - inline void ClearBits(uintptr_t reg, u32 mask) { - ClearBits(reinterpret_cast(reg), mask); - } - - inline void MaskBits(volatile u32 *reg, u32 mask) { - *reg = *reg & mask; - } - - inline void MaskBits(uintptr_t reg, u32 mask) { - MaskBits(reinterpret_cast(reg), mask); - } - - inline void ReadWrite(volatile u32 *reg, u32 val, u32 mask) { - *reg = (*reg & (~mask)) | (val & mask); - } - - inline void ReadWrite(uintptr_t reg, u32 val, u32 mask) { - ReadWrite(reinterpret_cast(reg), val, mask); - } - -} \ No newline at end of file diff --git a/libraries/libstratosphere/source/dd/dd_io_mappings.cpp b/libraries/libstratosphere/source/dd/dd_io_mappings.cpp deleted file mode 100644 index 8bc84442c..000000000 --- a/libraries/libstratosphere/source/dd/dd_io_mappings.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include - -namespace ams::dd { - - uintptr_t QueryIoMapping(uintptr_t phys_addr, size_t size) { - u64 virtual_addr; - const u64 aligned_addr = util::AlignDown(phys_addr, os::MemoryPageSize); - const size_t offset = phys_addr - aligned_addr; - const u64 aligned_size = size + offset; - if (hos::GetVersion() >= hos::Version_10_0_0) { - u64 region_size; - R_TRY_CATCH(svcQueryIoMapping(&virtual_addr, ®ion_size, aligned_addr, aligned_size)) { - /* Official software handles this by returning 0. */ - R_CATCH(svc::ResultNotFound) { return 0; } - } R_END_TRY_CATCH_WITH_ABORT_UNLESS; - AMS_ASSERT(region_size >= aligned_size); - } else { - R_TRY_CATCH(svcLegacyQueryIoMapping(&virtual_addr, aligned_addr, aligned_size)) { - /* Official software handles this by returning 0. */ - R_CATCH(svc::ResultNotFound) { return 0; } - } R_END_TRY_CATCH_WITH_ABORT_UNLESS; - } - - return static_cast(virtual_addr + offset); - } - - namespace { - - inline u32 ReadWriteRegisterImpl(uintptr_t phys_addr, u32 value, u32 mask) { - u32 out_value; - R_ABORT_UNLESS(svcReadWriteRegister(&out_value, phys_addr, mask, value)); - return out_value; - } - - } - - u32 ReadRegister(uintptr_t phys_addr) { - return ReadWriteRegisterImpl(phys_addr, 0, 0); - } - - void WriteRegister(uintptr_t phys_addr, u32 value) { - ReadWriteRegisterImpl(phys_addr, value, ~u32()); - } - - u32 ReadWriteRegister(uintptr_t phys_addr, u32 value, u32 mask) { - return ReadWriteRegisterImpl(phys_addr, value, mask); - } - -} diff --git a/libraries/libvapours/include/vapours.hpp b/libraries/libvapours/include/vapours.hpp index 6dda2b23d..eebb4f838 100644 --- a/libraries/libvapours/include/vapours.hpp +++ b/libraries/libvapours/include/vapours.hpp @@ -25,6 +25,7 @@ #include #include +#include #include #include diff --git a/libraries/libvapours/include/vapours/dd.hpp b/libraries/libvapours/include/vapours/dd.hpp index 38d3f1b71..52d3e9cd0 100644 --- a/libraries/libvapours/include/vapours/dd.hpp +++ b/libraries/libvapours/include/vapours/dd.hpp @@ -19,4 +19,6 @@ #include #include -#include +#include +#include +#include diff --git a/libraries/libvapours/include/vapours/dd/dd_cache.hpp b/libraries/libvapours/include/vapours/dd/dd_cache.hpp new file mode 100644 index 000000000..8ea0e200a --- /dev/null +++ b/libraries/libvapours/include/vapours/dd/dd_cache.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::dd { + + void InvalidateDataCache(void *addr, size_t size); + void StoreDataCache(void *addr, size_t size); + void FlushDataCache(void *addr, size_t size); + +} diff --git a/libraries/libvapours/include/vapours/dd/dd_common_types.hpp b/libraries/libvapours/include/vapours/dd/dd_common_types.hpp new file mode 100644 index 000000000..25a533b0f --- /dev/null +++ b/libraries/libvapours/include/vapours/dd/dd_common_types.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include + +namespace ams::dd { + + using PhysicalAddress = u64; + using DeviceVirtualAddress = u64; + + static_assert(std::same_as); + +} diff --git a/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp b/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp new file mode 100644 index 000000000..520575b03 --- /dev/null +++ b/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::dd { + + uintptr_t QueryIoMapping(dd::PhysicalAddress phys_addr, size_t size); + +} diff --git a/libraries/libexosphere/include/exosphere/reg.hpp b/libraries/libvapours/include/vapours/reg.hpp similarity index 59% rename from libraries/libexosphere/include/exosphere/reg.hpp rename to libraries/libvapours/include/vapours/reg.hpp index 1ac314110..c112908e1 100644 --- a/libraries/libexosphere/include/exosphere/reg.hpp +++ b/libraries/libvapours/include/vapours/reg.hpp @@ -48,100 +48,132 @@ namespace ams::reg { return (EncodeValue(values) | ...); } - ALWAYS_INLINE void Write(volatile u32 *reg, u32 val) { *reg = val; } - ALWAYS_INLINE void Write(volatile u32 ®, u32 val) { reg = val; } + template requires std::unsigned_integral + ALWAYS_INLINE void Write(volatile IntType *reg, std::type_identity_t val) { *reg = val; } + + template requires std::unsigned_integral + ALWAYS_INLINE void Write(volatile IntType ®, std::type_identity_t val) { reg = val; } + ALWAYS_INLINE void Write(uintptr_t reg, u32 val) { Write(reinterpret_cast(reg), val); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void Write(volatile u32 *reg, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); } + template requires std::unsigned_integral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void Write(volatile IntType *reg, const Values... values) { return Write(reg, static_cast((EncodeValue(values) | ...))); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void Write(volatile u32 ®, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); } + template requires std::unsigned_integral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void Write(volatile IntType ®, const Values... values) { return Write(reg, static_cast((EncodeValue(values) | ...))); } template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) ALWAYS_INLINE void Write(uintptr_t reg, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); } - ALWAYS_INLINE u32 Read(volatile u32 *reg) { return *reg; } - ALWAYS_INLINE u32 Read(volatile u32 ®) { return reg; } + template requires std::unsigned_integral + ALWAYS_INLINE IntType Read(volatile IntType *reg) { return *reg; } + + template requires std::unsigned_integral + ALWAYS_INLINE IntType Read(volatile IntType ®) { return reg; } + ALWAYS_INLINE u32 Read(uintptr_t reg) { return Read(reinterpret_cast(reg)); } - ALWAYS_INLINE u32 Read(volatile u32 *reg, u32 mask) { return *reg & mask; } - ALWAYS_INLINE u32 Read(volatile u32 ®, u32 mask) { return reg & mask; } + template requires std::unsigned_integral + ALWAYS_INLINE IntType Read(volatile IntType *reg, std::type_identity_t mask) { return *reg & mask; } + + template requires std::unsigned_integral + ALWAYS_INLINE IntType Read(volatile IntType ®, std::type_identity_t mask) { return reg & mask; } + ALWAYS_INLINE u32 Read(uintptr_t reg, u32 mask) { return Read(reinterpret_cast(reg), mask); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE u32 Read(volatile u32 *reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } + template requires std::unsigned_integral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE IntType Read(volatile IntType *reg, const Masks... masks) { return Read(reg, static_cast((EncodeMask(masks) | ...))); } + + template requires std::unsigned_integral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE IntType Read(volatile IntType ®, const Masks... masks) { return Read(reg, static_cast((EncodeMask(masks) | ...))); } template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE u32 Read(volatile u32 ®, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } + ALWAYS_INLINE u32 Read(uintptr_t reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE u32 Read(uintptr_t reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } + template requires std::unsigned_integral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE bool HasValue(volatile IntType *reg, const Values... values) { return Read(reg, static_cast((EncodeMask(values) | ...))) == static_cast(Encode(values...)); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE bool HasValue(volatile u32 *reg, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); } - - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE bool HasValue(volatile u32 ®, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); } + template requires std::unsigned_integral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE bool HasValue(volatile IntType ®, const Values... values) { return Read(reg, static_cast((EncodeMask(values) | ...))) == static_cast(Encode(values...)); } template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) ALWAYS_INLINE bool HasValue(uintptr_t reg, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); } - ALWAYS_INLINE u32 GetValue(volatile u32 *reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } - ALWAYS_INLINE u32 GetValue(volatile u32 ®, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } - ALWAYS_INLINE u32 GetValue(uintptr_t reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + template requires std::unsigned_integral + ALWAYS_INLINE IntType GetValue(volatile IntType *reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + template requires std::unsigned_integral + ALWAYS_INLINE IntType GetValue(volatile IntType ®, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + ALWAYS_INLINE u32 GetValue(uintptr_t reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + template requires std::unsigned_integral + ALWAYS_INLINE void ReadWrite(volatile IntType *reg, std::type_identity_t val, std::type_identity_t mask) { *reg = (*reg & (~mask)) | (val & mask); } + + template requires std::unsigned_integral + ALWAYS_INLINE void ReadWrite(volatile IntType ®, std::type_identity_t val, std::type_identity_t mask) { reg = ( reg & (~mask)) | (val & mask); } - ALWAYS_INLINE void ReadWrite(volatile u32 *reg, u32 val, u32 mask) { *reg = (*reg & (~mask)) | (val & mask); } - ALWAYS_INLINE void ReadWrite(volatile u32 ®, u32 val, u32 mask) { reg = ( reg & (~mask)) | (val & mask); } ALWAYS_INLINE void ReadWrite(uintptr_t reg, u32 val, u32 mask) { ReadWrite(reinterpret_cast(reg), val, mask); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ReadWrite(volatile u32 *reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } + template requires std::unsigned_integral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void ReadWrite(volatile IntType *reg, const Values... values) { return ReadWrite(reg, static_cast((EncodeValue(values) | ...)), static_cast((EncodeMask(values) | ...))); } + + template requires std::unsigned_integral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void ReadWrite(volatile IntType ®, const Values... values) { return ReadWrite(reg, static_cast((EncodeValue(values) | ...)), static_cast((EncodeMask(values) | ...))); } template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ReadWrite(volatile u32 ®, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } + ALWAYS_INLINE void ReadWrite(uintptr_t reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ReadWrite(uintptr_t reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } + template requires std::unsigned_integral + ALWAYS_INLINE void SetBits(volatile IntType *reg, std::type_identity_t mask) { *reg = *reg | mask; } + + template requires std::unsigned_integral + ALWAYS_INLINE void SetBits(volatile IntType ®, std::type_identity_t mask) { reg = reg | mask; } - ALWAYS_INLINE void SetBits(volatile u32 *reg, u32 mask) { *reg = *reg | mask; } - ALWAYS_INLINE void SetBits(volatile u32 ®, u32 mask) { reg = reg | mask; } ALWAYS_INLINE void SetBits(uintptr_t reg, u32 mask) { SetBits(reinterpret_cast(reg), mask); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void SetBits(volatile u32 *reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } + template requires std::unsigned_integral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void SetBits(volatile IntType *reg, const Masks... masks) { return SetBits(reg, static_cast((EncodeMask(masks) | ...))); } + + template requires std::unsigned_integral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void SetBits(volatile IntType ®, const Masks... masks) { return SetBits(reg, static_cast((EncodeMask(masks) | ...))); } template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void SetBits(volatile u32 ®, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } + ALWAYS_INLINE void SetBits(uintptr_t reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void SetBits(uintptr_t reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } + template requires std::unsigned_integral + ALWAYS_INLINE void ClearBits(volatile IntType *reg, std::type_identity_t mask) { *reg = *reg & ~mask; } + + template requires std::unsigned_integral + ALWAYS_INLINE void ClearBits(volatile IntType ®, std::type_identity_t mask) { reg = reg & ~mask; } - ALWAYS_INLINE void ClearBits(volatile u32 *reg, u32 mask) { *reg = *reg & ~mask; } - ALWAYS_INLINE void ClearBits(volatile u32 ®, u32 mask) { reg = reg & ~mask; } ALWAYS_INLINE void ClearBits(uintptr_t reg, u32 mask) { ClearBits(reinterpret_cast(reg), mask); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ClearBits(volatile u32 *reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } + template requires std::unsigned_integral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void ClearBits(volatile IntType *reg, const Masks... masks) { return ClearBits(reg, static_cast((EncodeMask(masks) | ...))); } + + template requires std::unsigned_integral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void ClearBits(volatile IntType ®, const Masks... masks) { return ClearBits(reg, static_cast((EncodeMask(masks) | ...))); } template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ClearBits(volatile u32 ®, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } + ALWAYS_INLINE void ClearBits(uintptr_t reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ClearBits(uintptr_t reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } + template requires std::unsigned_integral + ALWAYS_INLINE void MaskBits(volatile IntType *reg, std::type_identity_t mask) { *reg = *reg & mask; } + + template requires std::unsigned_integral + ALWAYS_INLINE void MaskBits(volatile IntType ®, std::type_identity_t mask) { reg = reg & mask; } - ALWAYS_INLINE void MaskBits(volatile u32 *reg, u32 mask) { *reg = *reg & mask; } - ALWAYS_INLINE void MaskBits(volatile u32 ®, u32 mask) { reg = reg & mask; } ALWAYS_INLINE void MaskBits(uintptr_t reg, u32 mask) { MaskBits(reinterpret_cast(reg), mask); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void MaskBits(volatile u32 *reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } + template requires std::unsigned_integral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void MaskBits(volatile IntType *reg, const Masks... masks) { return MaskBits(reg, static_cast((EncodeMask(masks) | ...))); } + + template requires std::unsigned_integral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void MaskBits(volatile IntType ®, const Masks... masks) { return MaskBits(reg, static_cast((EncodeMask(masks) | ...))); } template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void MaskBits(volatile u32 ®, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } - - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void MaskBits(uintptr_t reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } + ALWAYS_INLINE void MaskBits(uintptr_t reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } #define REG_BITS_MASK(OFFSET, WIDTH) ::ams::reg::BitsMask{OFFSET, WIDTH} #define REG_BITS_VALUE(OFFSET, WIDTH, VALUE) ::ams::reg::BitsValue{OFFSET, WIDTH, VALUE} diff --git a/libraries/libvapours/include/vapours/results.hpp b/libraries/libvapours/include/vapours/results.hpp index 4d4c41a56..40d809b90 100644 --- a/libraries/libvapours/include/vapours/results.hpp +++ b/libraries/libvapours/include/vapours/results.hpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libvapours/include/vapours/results/sdmmc_results.hpp b/libraries/libvapours/include/vapours/results/sdmmc_results.hpp new file mode 100644 index 000000000..ad63fbd30 --- /dev/null +++ b/libraries/libvapours/include/vapours/results/sdmmc_results.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::sdmmc { + + R_DEFINE_NAMESPACE_RESULT_MODULE(24); + + R_DEFINE_ERROR_RESULT(NotActivated, 2); + R_DEFINE_ERROR_RESULT(DeviceRemoved, 3); + R_DEFINE_ERROR_RESULT(NotAwakened, 4); + + R_DEFINE_ERROR_RANGE(CommunicationError, 32, 126); + R_DEFINE_ERROR_RANGE(CommunicationNotAttained, 33, 46); + R_DEFINE_ERROR_RESULT(ResponseIndexError, 34); + R_DEFINE_ERROR_RESULT(ResponseEndBitError, 35); + R_DEFINE_ERROR_RESULT(ResponseCrcError, 36); + R_DEFINE_ERROR_RESULT(ResponseTimeoutError, 37); + R_DEFINE_ERROR_RESULT(DataEndBitError, 38); + R_DEFINE_ERROR_RESULT(DataCrcError, 39); + R_DEFINE_ERROR_RESULT(DataTimeoutError, 40); + R_DEFINE_ERROR_RESULT(AutoCommandResponseIndexError, 41); + R_DEFINE_ERROR_RESULT(AutoCommandResponseEndBitError, 42); + R_DEFINE_ERROR_RESULT(AutoCommandResponseCrcError, 43); + R_DEFINE_ERROR_RESULT(AutoCommandResponseTimeoutError, 44); + R_DEFINE_ERROR_RESULT(CommandCompleteSoftwareTimeout, 45); + R_DEFINE_ERROR_RESULT(TransferCompleteSoftwareTimeout, 46); + R_DEFINE_ERROR_RESULT(AbortTransactionSoftwareTimeout, 74); + R_DEFINE_ERROR_RESULT(CommandInhibitCmdSoftwareTimeout, 75); + R_DEFINE_ERROR_RESULT(CommandInhibitDatSoftwareTimeout, 76); + R_DEFINE_ERROR_RESULT(BusySoftwareTimeout, 77); + + R_DEFINE_ERROR_RANGE(HostControllerUnexpected, 128, 158); + R_DEFINE_ERROR_RESULT(SdHostStandardUnknownAutoCmdError, 130); + R_DEFINE_ERROR_RESULT(SdHostStandardUnknownError, 131); + + R_DEFINE_ERROR_RANGE(InternalError, 160, 190); + R_DEFINE_ERROR_RESULT(NoWaitedInterrupt, 161); + R_DEFINE_ERROR_RESULT(WaitInterruptSoftwareTimeout, 162); + + R_DEFINE_ERROR_RESULT(NotImplemented, 201); +} diff --git a/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp b/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp index 2821e9e17..2e5e7115d 100644 --- a/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp +++ b/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp @@ -27,19 +27,25 @@ //#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS //#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL //#define AMS_SDMMC_USE_OS_EVENTS + //#define AMS_SDMMC_USE_OS_TIMER + #define AMS_SDMMC_USE_UTIL_TIMER #elif defined(ATMOSPHERE_IS_MESOSPHERE) //#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS //#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL //#define AMS_SDMMC_USE_OS_EVENTS + //#define AMS_SDMMC_USE_OS_TIMER + #define AMS_SDMMC_USE_UTIL_TIMER #elif defined(ATMOSPHERE_IS_STRATOSPHERE) #define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS #define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL #define AMS_SDMMC_USE_OS_EVENTS + #define AMS_SDMMC_USE_OS_TIMER + //#define AMS_SDMMC_USE_UTIL_TIMER #else #error "Unknown execution context for ams::sdmmc!" -#endif \ No newline at end of file +#endif diff --git a/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp b/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp index 7bbbc0375..3c6752e8f 100644 --- a/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp +++ b/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp @@ -17,7 +17,6 @@ #pragma once #include - namespace ams::sdmmc { enum BusPower { @@ -110,6 +109,4 @@ namespace ams::sdmmc { void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size, Port port); - - } \ No newline at end of file diff --git a/libraries/libvapours/include/vapours/util.hpp b/libraries/libvapours/include/vapours/util.hpp index 0d60a3ea7..51d9d74d3 100644 --- a/libraries/libvapours/include/vapours/util.hpp +++ b/libraries/libvapours/include/vapours/util.hpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libvapours/include/vapours/util/util_alignment.hpp b/libraries/libvapours/include/vapours/util/util_alignment.hpp index 849e141c0..3fa951504 100644 --- a/libraries/libvapours/include/vapours/util/util_alignment.hpp +++ b/libraries/libvapours/include/vapours/util/util_alignment.hpp @@ -73,4 +73,9 @@ namespace ams::util { return IsAligned(reinterpret_cast(value), alignment); } -} \ No newline at end of file + template requires std::integral && std::integral + constexpr ALWAYS_INLINE T DivideUp(T x, U y) { + return (x + (y - 1)) / y; + } + +} diff --git a/libraries/libvapours/include/vapours/dd/dd_device_virtual_address.hpp b/libraries/libvapours/include/vapours/util/util_timer.hpp similarity index 89% rename from libraries/libvapours/include/vapours/dd/dd_device_virtual_address.hpp rename to libraries/libvapours/include/vapours/util/util_timer.hpp index 0d4b971f0..2d2236163 100644 --- a/libraries/libvapours/include/vapours/dd/dd_device_virtual_address.hpp +++ b/libraries/libvapours/include/vapours/util/util_timer.hpp @@ -17,10 +17,10 @@ #pragma once #include #include -#include -namespace ams::dd { +namespace ams::util { - using DeviceVirtualAddress = u64; + u32 GetMicroSeconds(); + void WaitMicroSeconds(int us); } diff --git a/libraries/libvapours/source/dd/dd_cache.cpp b/libraries/libvapours/source/dd/dd_cache.cpp new file mode 100644 index 000000000..9b6073a06 --- /dev/null +++ b/libraries/libvapours/source/dd/dd_cache.cpp @@ -0,0 +1,37 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#else +#include +#endif +#include "impl/dd_select_cache_impl.hpp" + +namespace ams::dd { + + void InvalidateDataCache(void *addr, size_t size) { + return impl::InvalidateDataCacheImpl(addr, size); + } + + void StoreDataCache(void *addr, size_t size) { + return impl::StoreDataCacheImpl(addr, size); + } + + void FlushDataCache(void *addr, size_t size) { + return impl::FlushDataCacheImpl(addr, size); + } + +} diff --git a/libraries/libvapours/source/dd/dd_io_mapping.cpp b/libraries/libvapours/source/dd/dd_io_mapping.cpp new file mode 100644 index 000000000..c029a0434 --- /dev/null +++ b/libraries/libvapours/source/dd/dd_io_mapping.cpp @@ -0,0 +1,69 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#else +#include +#endif + +namespace ams::dd { + + uintptr_t QueryIoMapping(dd::PhysicalAddress phys_addr, size_t size) { + #if defined(ATMOSPHERE_IS_EXOSPHERE) + #if defined(ATMOSPHERE_ARCH_ARM64) + /* TODO: Secure Monitor translation? */ + AMS_UNUSED(size); + return static_cast(phys_addr); + #elif defined(ATMOSPHERE_ARCH_ARM) + /* TODO: BPMP translation? */ + AMS_UNUSED(size); + return static_cast(phys_addr); + #else + #error "Unknown architecture for ams::dd::QueryIoMapping (EXOSPHERE)!" + #endif + #elif defined(ATMOSPHERE_IS_MESOSPHERE) + /* TODO: Kernel address translation? */ + AMS_UNUSED(size); + return static_cast(phys_addr); + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + svc::Address virt_addr = 0; + const dd::PhysicalAddress aligned_addr = util::AlignDown(phys_addr, os::MemoryPageSize); + const size_t offset = phys_addr - aligned_addr; + const size_t aligned_size = size + offset; + + if (hos::GetVersion() >= hos::Version_10_0_0) { + svc::Size region_size = 0; + R_TRY_CATCH(svc::QueryIoMapping(&virt_addr, ®ion_size, aligned_addr, aligned_size)) { + /* Official software handles this by returning 0. */ + R_CATCH(svc::ResultNotFound) { return 0; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + AMS_ASSERT(region_size >= aligned_size); + } else { + R_TRY_CATCH(svc::LegacyQueryIoMapping(&virt_addr, aligned_addr, aligned_size)) { + /* Official software handles this by returning 0. */ + R_CATCH(svc::ResultNotFound) { return 0; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + return static_cast(virt_addr) + offset; + + #else + #error "Unknown execution context for ams::dd::QueryIoMapping!" + #endif + } + +} diff --git a/libraries/libvapours/source/dd/impl/dd_cache_impl.os.horizon.hpp b/libraries/libvapours/source/dd/impl/dd_cache_impl.os.horizon.hpp new file mode 100644 index 000000000..53ccf6473 --- /dev/null +++ b/libraries/libvapours/source/dd/impl/dd_cache_impl.os.horizon.hpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#else +#include +#endif + +namespace ams::dd::impl { + + void StoreDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc cvac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #else + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + /* Invoke the relevant svc. */ + const auto result = svc::StoreProcessDataCache(svc::PseudoHandle::CurrentProcess, reinterpret_cast(addr), size); + R_ASSERT(result); + #elif defined(ATMOSPHERE_IS_EXOSPHERE) && defined(__BPMP__) + /* Do nothing. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown execution context for ams::dd::impl::StoreDataCacheImpl" + #endif + #endif + } + + void FlushDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc civac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #else + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + /* Invoke the relevant svc. */ + const auto result = svc::FlushProcessDataCache(svc::PseudoHandle::CurrentProcess, reinterpret_cast(addr), size); + R_ASSERT(result); + #elif defined(ATMOSPHERE_IS_EXOSPHERE) && defined(__BPMP__) + /* Do nothing. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown execution context for ams::dd::impl::FlushDataCacheImpl" + #endif + #endif + } + + void InvalidateDataCacheImpl(void *addr, size_t size) { + /* Just perform a flush, which is clean + invalidate. */ + return FlushDataCacheImpl(addr, size); + } + +} diff --git a/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp b/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp new file mode 100644 index 000000000..18e021496 --- /dev/null +++ b/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "dd_cache_impl.os.horizon.hpp" +#else + #error "Unknown OS for ams::dd::CacheImpl" +#endif diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp index 290d5c637..29ac27601 100644 --- a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp @@ -14,7 +14,6 @@ * along with this program. If not, see . */ #pragma once - #include namespace ams::sdmmc::impl::ClockResetController { diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp index 8468c981a..a1980813d 100644 --- a/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp @@ -14,7 +14,6 @@ * along with this program. If not, see . */ #pragma once - #include #include "sdmmc_i_host_controller.hpp" diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp index 2d6857f87..52c53fafe 100644 --- a/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp @@ -14,7 +14,6 @@ * along with this program. If not, see . */ #pragma once - #include #if defined(AMS_SDMMC_USE_OS_EVENTS) @@ -78,12 +77,62 @@ namespace ams::sdmmc::impl { virtual void Initialize() = 0; virtual void Finalize() = 0; - /* TODO */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + #endif + + virtual void SetWorkBuffer(void *wb, size_t wb_size) = 0; + + virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) = 0; + virtual void Shutdown(); + virtual void PutToSleep(); + virtual Result Awaken(); + + virtual Result SwitchToSdr12(); + + virtual bool IsSupportedBusPower(BusPower bus_power) const = 0; + virtual BusPower GetBusPower() const = 0; + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const = 0; + virtual void SetBusWidth(BusWidth bus_width) = 0; + virtual BusWidth GetBusWidth() const = 0; + + virtual Result SetSpeedMode(SpeedMode speed_mode) = 0; + virtual SpeedMode GetSpeedMode() const = 0; + + virtual u32 GetDeviceClockFrequencyKHz() const = 0; + + virtual void SetPowerSaving(bool en) = 0; + virtual bool IsPowerSavingEnable() const = 0; + + virtual void EnableDeviceClock() = 0; + virtual void DisableDeviceClock() = 0; + virtual bool IsDeviceClockEnable() const = 0; + + virtual u32 GetMaxTransferNumBlocks() const = 0; virtual void ChangeCheckTransferInterval(u32 ms) = 0; virtual void SetDefaultCheckTransferInterval() = 0; - /* TODO */ + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) = 0; + virtual Result IssueStopTransmissionCommand(u32 *out_response) = 0; + + ALWAYS_INLINE Result IssueCommand(const Command *command, TransferData *xfer_data) { + return this->IssueCommand(command, xfer_data, nullptr); + } + + ALWAYS_INLINE Result IssueCommand(const Command *command) { + return this->IssueCommand(command, nullptr, nullptr); + } + + virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const = 0; + virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const = 0; + + virtual bool IsSupportedTuning() const = 0; + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) = 0; + virtual void SaveTuningStatusForHs400() = 0; + virtual Result GetInternalStatus() const = 0; }; } diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp new file mode 100644 index 000000000..3d7aab91d --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sdmmc_port_mmc0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForMmc g_mmc0_host_controller; + + } + + IHostController *GetHostControllerOfPortMmc0() { + return std::addressof(g_mmc0_host_controller); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp index 1ceb1f101..8f1959ed2 100644 --- a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp @@ -14,7 +14,6 @@ * along with this program. If not, see . */ #pragma once - #include #include "sdmmc_i_host_controller.hpp" #include "sdmmc_i_device_accessor.hpp" diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp new file mode 100644 index 000000000..7d055b34c --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp @@ -0,0 +1,947 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sdmmc_sd_host_standard_controller.hpp" +#include "sdmmc_timer.hpp" + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) + #include +#endif + + +namespace ams::sdmmc::impl { + + namespace { + + constexpr inline u32 ControllerReactionTimeoutMilliSeconds = 2000; + constexpr inline u32 CommandTimeoutMilliSeconds = 2000; + constexpr inline u32 DefaultCheckTransferIntervalMilliSeconds = 1500; + constexpr inline u32 BusyTimeoutMilliSeconds = 2000; + + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void SdHostStandardController::ResetBufferInfos() { + for (auto &info : this->buffer_infos) { + info.buffer_address = 0; + info.buffer_size = 0; + } + } + + dd::DeviceVirtualAddress SdHostStandardController::GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size) { + /* Try to find the buffer in our registered regions. */ + dd::DeviceVirtualAddress device_addr = 0; + for (const auto &info : this->buffer_infos) { + if (info.buffer_address <= buffer && (buffer + buffer_size) <= (info.buffer_address + info.buffer_size)) { + device_addr = info.buffer_device_virtual_address + (buffer - info.buffer_address); + break; + } + } + + /* Ensure that we found the buffer. */ + AMS_ABORT_UNLESS(device_addr != 0); + return device_addr; + } + #endif + + void SdHostStandardController::EnsureControl() { + /* Perform a read of clock control to be sure previous configuration takes. */ + reg::Read(this->registers->clock_control); + } + + void SdHostStandardController::EnableInterruptStatus() { + /* Set the status register interrupt enables. */ + reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); + reg::ReadWrite(this->registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); + + /* Read/write the interrupt enables to be sure they take. */ + reg::Write(this->registers->normal_int_enable, reg::Read(this->registers->normal_int_enable)); + reg::Write(this->registers->error_int_enable, reg::Read(this->registers->error_int_enable)); + + /* If we're using interrupt events, configure appropriately. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Clear interrupts. */ + this->ClearInterrupt(); + + /* Enable the interrupt signals. */ + reg::ReadWrite(this->registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); + reg::ReadWrite(this->registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); + } + #endif + } + + void SdHostStandardController::DisableInterruptStatus() { + /* If we're using interrupt events, configure appropriately. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Disable the interrupt signals. */ + reg::ReadWrite(this->registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); + reg::ReadWrite(this->registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); + } + #endif + + /* Mask the status register interrupt enables. */ + reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); + reg::ReadWrite(this->registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + Result SdHostStandardController::WaitInterrupt(u32 timeout_ms) { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait for the interrupt to be signaled. */ + os::WaitableHolderType *signaled_holder = os::TimedWaitAny(std::addressof(this->waitable_manager), TimeSpan::FromMilliSeconds(timeout_ms)); + if (signaled_holder == std::addressof(this->interrupt_event_holder)) { + /* We received the interrupt. */ + return ResultSuccess(); + } else if (signaled_holder == std::addressof(this->removed_event_holder)) { + /* The device was removed. */ + return sdmmc::ResultDeviceRemoved(); + } else { + /* Timeout occurred. */ + return sdmmc::ResultWaitInterruptSoftwareTimeout(); + } + } + + void SdHostStandardController::ClearInterrupt() { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Clear the interrupt event. */ + os::ClearInterruptEvent(this->interrupt_event); + } + #endif + + void SdHostStandardController::SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data) { + /* Ensure the transfer data is valid. */ + AMS_ABORT_UNLESS(xfer_data->block_size != 0); + AMS_ABORT_UNLESS(xfer_data->num_blocks != 0); + + /* Determine the number of blocks. */ + const u16 num_blocks = std::min(xfer_data->num_blocks, SdHostStandardRegisters::BlockCountMax); + + /* Determine the address/how many blocks to transfer. */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + const u64 address = this->GetDeviceVirtualAddress(reinterpret_cast(xfer_data->buffer), xfer_data->block_size * num_blocks); + const u16 num_xfer_blocks = num_blocks; + #else + const u64 address = reinterpret_cast(xfer_data->buffer); + const u16 num_xfer_blocks = num_blocks; + #endif + + /* Verify the address is usable. */ + AMS_ABORT_UNLESS(util::IsAligned(address, BufferDeviceVirtualAddressAlignment)); + + /* Configure for sdma. */ + reg::Write(this->registers->adma_address, static_cast(address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(address >> BITSIZEOF(u32))); + + /* Set our next sdma address. */ + this->next_sdma_address = util::AlignDown(address + SdmaBufferBoundary, SdmaBufferBoundary); + + /* Configure block size. */ + AMS_ABORT_UNLESS(xfer_data->block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); + reg::Write(this->registers->block_size, SD_REG_BITS_ENUM (BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, FIVE_TWELVE_KB), + SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, static_cast(xfer_data->block_size))); + + /* Configure transfer blocks. */ + reg::Write(this->registers->block_count, num_xfer_blocks); + if (out_num_transferred_blocks != nullptr) { + *out_num_transferred_blocks = num_xfer_blocks; + } + + /* Configure transfer mode. */ + reg::Write(this->registers->transfer_mode, SD_REG_BITS_ENUM (TRANSFER_MODE_DMA_ENABLE, ENABLE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_BLOCK_COUNT_ENABLE, (xfer_data->is_multi_block_transfer), ENABLE, DISABLE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_MULTI_BLOCK_SELECT, (xfer_data->is_multi_block_transfer), MULTI_BLOCK, SINGLE_BLOCK), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, (xfer_data->transfer_direction == TransferDirection_ReadFromDevice), READ, WRITE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_AUTO_CMD_ENABLE, (xfer_data->is_stop_transmission_command_enabled), CMD12_ENABLE, DISABLE)); + } + + void SdHostStandardController::SetCommand(const Command *command, bool has_xfer_data) { + /* Encode the command value. */ + u16 command_val = 0; + + /* Encode the response type. */ + switch (command->response_type) { + case ResponseType_R0: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, NO_RESPONSE), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + case ResponseType_R1: + case ResponseType_R6: + case ResponseType_R7: + command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_RESPONSE_TYPE, command->is_busy, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE, RESPONSE_LENGTH_48), + SD_REG_BITS_ENUM (COMMAND_CRC_CHECK, ENABLE), + SD_REG_BITS_ENUM (COMMAND_INDEX_CHECK, ENABLE)); + break; + case ResponseType_R2: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_136), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, ENABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + case ResponseType_R3: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_48), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Encode the data present select. */ + command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_DATA_PRESENT, has_xfer_data, DATA_PRESENT, NO_DATA_PRESENT)); + + /* Encode the command index. */ + AMS_ABORT_UNLESS(command->command_index <= SdHostStandardCommandIndexMax); + command_val |= reg::Encode(SD_REG_BITS_VALUE(COMMAND_COMMAND_INDEX, command->command_index)); + + /* Write the command and argument. */ + reg::Write(this->registers->argument, command->command_argument); + reg::Write(this->registers->command, command_val); + } + + Result SdHostStandardController::ResetCmdDatLine() { + /* Set the software reset cmd/dat bits. */ + reg::ReadWrite(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, RESET), + SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, RESET)); + + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait until reset is done. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, WORK), + SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, WORK))) + { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + return sdmmc::ResultAbortTransactionSoftwareTimeout(); + } + } + } + + return ResultSuccess(); + } + + Result SdHostStandardController::AbortTransaction() { + R_TRY(this->ResetCmdDatLine()); + return ResultSuccess(); + } + + Result SdHostStandardController::WaitWhileCommandInhibit(bool has_dat) { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while command inhibit cmd is set. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, NOT_READY))) { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandInhibitCmdSoftwareTimeout(); + } + } + } + + /* Wait while command inhibit dat is set. */ + if (has_dat) { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, NOT_READY))) { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandInhibitDatSoftwareTimeout(); + } + } + } + + return ResultSuccess(); + } + + Result SdHostStandardController::CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask) { + /* Read the statuses. */ + volatile u16 normal_int_status = this->registers->normal_int_status; + volatile u16 error_int_status = this->registers->error_int_status; + volatile u16 auto_cmd_err_status = this->registers->acmd12_err; + + /* Set the output status, if necessary. */ + if (out_normal_int_status != nullptr) { + *out_normal_int_status = normal_int_status; + } + + /* If we don't have an error interrupt, just use the normal status. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, NO_ERROR))) { + /* If the wait mask has any bits set, we're done waiting for the interrupt. */ + const u16 masked_status = (normal_int_status & wait_mask); + R_UNLESS(masked_status != 0, sdmmc::ResultNoWaitedInterrupt()); + + /* Write the masked value to the status register to ensure consistent state. */ + reg::Write(this->registers->normal_int_status, masked_status); + return ResultSuccess(); + } + + /* We have an error interrupt. Write the status to the register to ensure consistent state. */ + reg::Write(this->registers->error_int_status, error_int_status); + + /* Check the error interrupt status bits, and return appropriate errors. */ + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, NO_ERROR)), sdmmc::ResultResponseIndexError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, NO_ERROR)), sdmmc::ResultResponseEndBitError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, NO_ERROR)), sdmmc::ResultResponseCrcError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, NO_ERROR)), sdmmc::ResultResponseTimeoutError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, NO_ERROR)), sdmmc::ResultDataEndBitError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, NO_ERROR)), sdmmc::ResultDataCrcError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, NO_ERROR)), sdmmc::ResultDataTimeoutError()); + + /* Check for auto cmd errors. */ + if (reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, ERROR))) { + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, NO_ERROR)), sdmmc::ResultAutoCommandResponseIndexError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, NO_ERROR)), sdmmc::ResultAutoCommandResponseEndBitError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, NO_ERROR)), sdmmc::ResultAutoCommandResponseCrcError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, NO_ERROR)), sdmmc::ResultAutoCommandResponseTimeoutError()); + + /* An known auto cmd error occurred. */ + return sdmmc::ResultSdHostStandardUnknownAutoCmdError(); + } else { + /* Unknown error occurred. */ + return sdmmc::ResultSdHostStandardUnknownError(); + } + } + + Result SdHostStandardController::WaitCommandComplete() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Wait for interrupt. */ + Result result = this->WaitInterrupt(CommandTimeoutMilliSeconds); + if (R_SUCCEEDED(result)) { + /* If we succeeded, check/clear our interrupt status. */ + result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); + this->ClearInterrupt(); + + if (R_FAILED(result)) { + this->AbortTransaction(); + } + + return result; + } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { + /* Otherwise, check if the device was removed. */ + return result; + } else { + /* If the device wasn't removed, cancel our transaction. */ + this->AbortTransaction(); + return sdmmc::ResultCommandCompleteSoftwareTimeout(); + } + } + #else + { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while command is not complete. */ + { + ManualTimer timer(CommandTimeoutMilliSeconds); + while (true) { + /* Check and clear the interrupt status. */ + const auto result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); + + /* If we succeeded, we're done. */ + if (R_SUCCEEDED(result)) { + return ResultSuccess(); + } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { + /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandCompleteSoftwareTimeout(); + } + } else { + /* Otherwise, we have a generic failure. */ + this->AbortTransaction(); + return result; + } + } + } + } + #endif + } + + Result SdHostStandardController::WaitTransferComplete() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Wait while transfer is not complete. */ + while (true) { + /* Get the last block count. */ + const u16 last_block_count = reg::Read(this->registers->block_count); + + /* Wait for interrupt. */ + Result result = this->WaitInterrupt(this->check_transfer_interval_ms); + if (R_SUCCEEDED(result)) { + /* If we succeeded, check/clear our interrupt status. */ + volatile u16 normal_int_status; + result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); + + this->ClearInterrupt(); + + /* If the interrupt succeeded, check status. */ + if (R_SUCCEEDED(result)) { + /* If the transfer is complete, we're done. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { + return ResultSuccess(); + } + + /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { + reg::Write(this->registers->adma_address, static_cast(this->next_sdma_address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(this->next_sdma_address >> BITSIZEOF(u32))); + + this->next_sdma_address += SdmaBufferBoundary; + } + } else { + /* Abort the transaction. */ + this->AbortTransaction(); + return result; + } + + return result; + } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { + /* Otherwise, check if the device was removed. */ + return result; + } else { + /* Otherwise, timeout if the transfer hasn't advanced. */ + if (last_block_count != reg::Read(this->registers->block_count)) { + this->AbortTransaction(); + return sdmmc::ResultTransferCompleteSoftwareTimeout(); + } + } + } + } + #else + { + /* Wait while transfer is not complete. */ + while (true) { + /* Get the last block count. */ + const u16 last_block_count = reg::Read(this->registers->block_count); + + /* Wait until transfer times out. */ + { + ManualTimer timer(this->check_transfer_interval_ms); + while (true) { + /* Check/clear our interrupt status. */ + volatile u16 normal_int_status; + const auto result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); + + /* If the check succeeded, check status. */ + if (R_SUCCEEDED(result)) { + /* If the transfer is complete, we're done. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { + return ResultSuccess(); + } + + /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { + reg::Write(this->registers->adma_address, static_cast(this->next_sdma_address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(this->next_sdma_address >> BITSIZEOF(u32))); + + this->next_sdma_address += SdmaBufferBoundary; + } + } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { + /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ + if (!timer.Update()) { + /* Only timeout if the transfer hasn't advanced. */ + if (last_block_count != reg::Read(this->registers->block_count)) { + this->AbortTransaction(); + return sdmmc::ResultTransferCompleteSoftwareTimeout(); + } + break; + } + } else { + /* Otherwise, we have a generic failure. */ + this->AbortTransaction(); + return result; + } + } + } + } + } + #endif + } + + Result SdHostStandardController::WaitWhileBusy() { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while busy. */ + { + ManualTimer timer(BusyTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* If the DAT0 line signal is level high, we're done. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, HIGH))) { + return ResultSuccess(); + } + + /* Otherwise, check if we're timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultBusySoftwareTimeout(); + } + } + } + } + + void SdHostStandardController::GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { + /* Check that we can write the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + + /* Get the response appropriately. */ + switch (response_type) { + case ResponseType_R1: + case ResponseType_R3: + case ResponseType_R6: + case ResponseType_R7: + /* 32-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); + out_response[0] = reg::Read(this->registers->response[0]); + break; + case ResponseType_R2: + /* 128-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); + out_response[0] = reg::Read(this->registers->response[0]); + out_response[1] = reg::Read(this->registers->response[1]); + out_response[2] = reg::Read(this->registers->response[2]); + out_response[3] = reg::Read(this->registers->response[3]); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result SdHostStandardController::IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* Wait until we can issue the command. */ + R_TRY(this->WaitWhileCommandInhibit((xfer_data != nullptr) || command->is_busy)); + + /* Configure for the transfer. */ + u32 num_transferred_blocks = 0; + if (xfer_data != nullptr) { + /* Setup the transfer, and get the number of blocks. */ + this->SetTransfer(std::addressof(num_transferred_blocks), xfer_data); + + /* Ensure the device sees consistent data with the cpu. */ + dd::FlushDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); + } + + /* Issue the command with interrupt status enabled. */ + { + this->EnableInterruptStatus(); + ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; + + /* Set the command. */ + this->SetCommand(command, xfer_data != nullptr); + + /* Wait for the command to complete. */ + R_TRY(this->WaitCommandComplete()); + + /* Process any response. */ + if (command->response_type != ResponseType_R0) { + this->last_response_type = command->response_type; + this->GetResponse(this->last_response, sizeof(this->last_response), this->last_response_type); + } + + /* Wait for data to be transferred. */ + if (xfer_data != nullptr) { + R_TRY(this->WaitTransferComplete()); + } + } + + /* If data was transferred, ensure we're in a consistent state. */ + if (xfer_data != nullptr) { + /* Ensure the cpu sees consistent data with the device. */ + if (xfer_data->transfer_direction == TransferDirection_ReadFromDevice) { + dd::InvalidateDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); + } + + /* Set the number of transferred blocks. */ + if (out_num_transferred_blocks != nullptr) { + *out_num_transferred_blocks = num_transferred_blocks; + } + + /* Process stop transition command. */ + this->last_stop_transmission_response = this->registers->response[3]; + } + + /* Wait until we're no longer busy. */ + if (command->is_busy || (xfer_data != nullptr)) { + R_TRY(this->WaitWhileBusy()); + } + + return ResultSuccess(); + } + + Result SdHostStandardController::IssueStopTransmissionCommandWithDeviceClock(u32 *out_response) { + /* Wait until we can issue the command. */ + R_TRY(this->WaitWhileCommandInhibit(false)); + + /* Issue the command with interrupt status enabled. */ + constexpr ResponseType StopTransmissionCommandResponseType = ResponseType_R1; + { + this->EnableInterruptStatus(); + ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; + + /* Set the command. */ + Command command(12, 0, StopTransmissionCommandResponseType, true); + this->SetCommand(std::addressof(command), false); + + /* Wait for the command to complete. */ + R_TRY(this->WaitCommandComplete()); + } + + /* Process response. */ + this->GetResponse(out_response, sizeof(u32), StopTransmissionCommandResponseType); + + /* Wait until we're done. */ + R_TRY(this->WaitWhileBusy()); + + return ResultSuccess(); + } + + SdHostStandardController::SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size) { + /* Translate the physical address to a address. */ + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + const uintptr_t registers_addr = dd::QueryIoMapping(registers_phys_addr, registers_size); + #else + /* TODO: Discriminate between bpmp, exosphere address? */ + AMS_UNUSED(registers_size); + const uintptr_t registers_addr = static_cast(registers_phys_addr); + #endif + + /* Set registers. */ + AMS_ABORT_UNLESS(registers_addr != 0); + this->registers = reinterpret_cast(registers_addr); + + /* Reset DMA buffers, if we have any. */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + this->ResetBufferInfos(); + #endif + + /* Clear removed event, if we have one. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + this->removed_event = nullptr; + #endif + + /* Clear dma address. */ + this->next_sdma_address = 0; + this->check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; + + /* Clear clock/power trackers. */ + this->device_clock_frequency_khz = 0; + this->is_power_saving_enable = false; + this->is_device_clock_enable = false; + + /* Clear last response. */ + this->last_response_type = ResponseType_R0; + std::memset(this->last_response, 0, sizeof(this->last_response)); + this->last_stop_transmission_response = 0; + } + + void SdHostStandardController::Initialize() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::InitializeWaitableManager(std::addressof(this->waitable_manager)); + + AMS_ABORT_UNLESS(this->interrupt_event != nullptr); + os::InitializeWaitableHolder(std::addressof(this->interrupt_event_holder), this->interrupt_event); + os::LinkWaitableHolder(std::addressof(this->waitable_manager), std::addressof(this->interrupt_event_holder)); + + if (this->removed_event != nullptr) { + os::InitializeWaitableHolder(std::addressof(this->removed_event_holder), this->removed_event); + os::LinkWaitableHolder(std::addressof(this->waitable_manager), std::addressof(this->removed_event_holder)); + } + } + #endif + } + + void SdHostStandardController::Finalize() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + if (this->removed_event != nullptr) { + os::UnlinkWaitableHolder(std::addressof(this->removed_event_holder)); + os::FinalizeWaitableHolder(std::addressof(this->removed_event_holder)); + } + + os::UnlinkWaitableHolder(std::addressof(this->interrupt_event_holder)); + os::FinalizeWaitableHolder(std::addressof(this->interrupt_event_holder)); + + os::FinalizeWaitableManager(std::addressof(this->waitable_manager)); + } + #endif + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void SdHostStandardController::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Find and set a free info. */ + for (auto &info : this->buffer_infos) { + if (info.buffer_address == 0) { + info = { + .buffer_address = buffer, + .buffer_size = buffer_size, + .buffer_device_virtual_address = buffer_device_virtual_address, + }; + return; + } + } + + AMS_ABORT("Out of BufferInfos\n"); + } + + void SdHostStandardController::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Find and clear the buffer info. */ + for (auto &info : this->buffer_infos) { + if (info.buffer_address == 0) { + AMS_ABORT_UNLESS(info.buffer_size == buffer_size); + AMS_ABORT_UNLESS(info.buffer_device_virtual_address == buffer_device_virtual_address); + + info.buffer_address = 0; + info.buffer_size = 0; + return; + } + } + + AMS_ABORT("BufferInfo not found\n"); + } + #endif + + void SdHostStandardController::SetWorkBuffer(void *wb, size_t wb_size) { + AMS_UNUSED(wb, wb_size); + AMS_ABORT("WorkBuffer is not needed\n"); + } + + BusPower SdHostStandardController::GetBusPower() const { + /* Check if the bus has power. */ + if (reg::HasValue(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON))) { + /* If it does, return the corresponding power. */ + switch (reg::GetValue(this->registers->power_control, SD_REG_BITS_MASK(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1))) { + case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_1_8V: + return BusPower_1_8V; + case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_3_3V: + return BusPower_3_3V; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + /* It doesn't, so it's off. */ + return BusPower_Off; + } + } + + void SdHostStandardController::SetBusWidth(BusWidth bus_width) { + /* Check that we support the bus width. */ + AMS_ABORT_UNLESS(this->IsSupportedBusWidth(bus_width)); + + /* Set the appropriate data transfer width. */ + switch (bus_width) { + case BusWidth_1Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, ONE_BIT)); + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); + break; + case BusWidth_4Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT)); + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); + break; + case BusWidth_8Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + BusWidth SdHostStandardController::GetBusWidth() const { + /* Check if the bus is using eight-bit extended data transfer. */ + if (reg::HasValue(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT))) { + return BusWidth_8Bit; + } else { + /* Bus is configured as USE_DATA_TRANSFER_WIDTH, so check if it's four bit. */ + if (reg::HasValue(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT))) { + return BusWidth_4Bit; + } else { + return BusWidth_1Bit; + } + } + } + + void SdHostStandardController::SetPowerSaving(bool en) { + /* Set whether we're power saving enable. */ + this->is_power_saving_enable = en; + + /* Configure accordingly. */ + if (this->is_power_saving_enable) { + /* We want to disable SD clock if it's enabled. */ + if (reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + } else { + /* We want to enable SD clock if it's disabled and we're supposed to enable device clock. */ + if (this->is_device_clock_enable && reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + } + } + + void SdHostStandardController::EnableDeviceClock() { + /* If we're not in power-saving mode and the device clock is disabled, enable it. */ + if (!this->is_power_saving_enable && reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + this->is_device_clock_enable = true; + } + + void SdHostStandardController::DisableDeviceClock() { + /* Unconditionally disable the device clock. */ + this->is_device_clock_enable = false; + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + void SdHostStandardController::ChangeCheckTransferInterval(u32 ms) { + this->check_transfer_interval_ms = ms; + } + + void SdHostStandardController::SetDefaultCheckTransferInterval() { + this->check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; + } + + Result SdHostStandardController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* We need to have device clock enabled to issue commands. */ + AMS_ABORT_UNLESS(this->is_device_clock_enable); + + /* Check if we need to temporarily re-enable the device clock. */ + bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that our configuration takes. */ + this->EnsureControl(); + + /* Wait 8 device clocks to be sure that it's usable. */ + WaitClocks(8, this->device_clock_frequency_khz); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Issue the command. */ + { + /* After we issue the command, we need to wait 8 device clocks. */ + ON_SCOPE_EXIT { WaitClocks(8, this->device_clock_frequency_khz); }; + + return this->IssueCommandWithDeviceClock(command, xfer_data, out_num_transferred_blocks); + } + } + + Result SdHostStandardController::IssueStopTransmissionCommand(u32 *out_response) { + /* We need to have device clock enabled to issue commands. */ + AMS_ABORT_UNLESS(this->is_device_clock_enable); + + /* Check if we need to temporarily re-enable the device clock. */ + bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that our configuration takes. */ + this->EnsureControl(); + + /* Wait 8 device clocks to be sure that it's usable. */ + WaitClocks(8, this->device_clock_frequency_khz); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Issue the command. */ + { + /* After we issue the command, we need to wait 8 device clocks. */ + ON_SCOPE_EXIT { WaitClocks(8, this->device_clock_frequency_khz); }; + + return this->IssueStopTransmissionCommandWithDeviceClock(out_response); + } + } + + void SdHostStandardController::GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { + /* Check that we can get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + AMS_ABORT_UNLESS(response_type == this->last_response_type); + + /* Get the response appropriately. */ + switch (response_type) { + case ResponseType_R1: + case ResponseType_R3: + case ResponseType_R6: + case ResponseType_R7: + /* 32-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); + out_response[0] = this->last_response[0]; + break; + case ResponseType_R2: + /* 128-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); + out_response[0] = this->last_response[0]; + out_response[1] = this->last_response[1]; + out_response[2] = this->last_response[2]; + out_response[3] = this->last_response[3]; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SdHostStandardController::GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const { + /* Check that we can get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + AMS_ABORT_UNLESS(response_size >= sizeof(u32)); + + /* Get the response. */ + out_response[0] = this->last_stop_transmission_response; + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp new file mode 100644 index 000000000..2caf3558a --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_sd_host_standard_registers.hpp" + +namespace ams::sdmmc::impl { + + class SdHostStandardController : public IHostController { + protected: + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + struct BufferInfo { + uintptr_t buffer_address; + size_t buffer_size; + dd::DeviceVirtualAddress buffer_device_virtual_address; + }; + + static constexpr inline auto NumBufferInfos = 3; + #endif + protected: + SdHostStandardRegisters *registers; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + BufferInfo buffer_infos[NumBufferInfos]; + #endif + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + os::WaitableManagerType waitable_manager; + os::InterruptEventType *interrupt_event; + os::WaitableHolderType interrupt_event_holder; + os::EventType *removed_event; + os::WaitableHolderType removed_event_holder; + #endif + + u64 next_sdma_address; + u32 check_transfer_interval_ms; + + u32 device_clock_frequency_khz; + bool is_power_saving_enable; + bool is_device_clock_enable; + + ResponseType last_response_type; + u32 last_response[4]; + u32 last_stop_transmission_response; + protected: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + void PreSetInterruptEvent(os::InterruptEventType *ie) { + this->interrupt_event = ie; + } + + bool IsRemoved() const { + return this->removed_event != nullptr && os::TryWaitEvent(this->removed_event); + } + #endif + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void ResetBufferInfos(); + dd::DeviceVirtualAddress GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size); + #endif + + void EnsureControl(); + + void EnableInterruptStatus(); + void DisableInterruptStatus(); + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + Result WaitInterrupt(u32 timeout_ms); + void ClearInterrupt(); + #endif + + void SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data); + + void SetCommand(const Command *command, bool has_xfer_data); + + Result ResetCmdDatLine(); + Result AbortTransaction(); + Result WaitWhileCommandInhibit(bool has_dat); + Result CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask); + Result WaitCommandComplete(); + Result WaitTransferComplete(); + Result WaitWhileBusy(); + + void GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const; + + Result IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks); + Result IssueStopTransmissionCommandWithDeviceClock(u32 *out_response); + + ALWAYS_INLINE Result CheckRemoved() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); + #endif + + return ResultSuccess(); + } + public: + SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size); + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual void PreSetRemovedEvent(os::EventType *e) override { + this->removed_event = e; + } + #endif + + virtual void Initialize() override; + virtual void Finalize() override; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + #endif + + virtual void SetWorkBuffer(void *wb, size_t wb_size) override; + + virtual Result SwitchToSdr12() override { + AMS_ABORT("SwitchToSdr12 not supported\n"); + } + + virtual BusPower GetBusPower() const override; + + virtual void SetBusWidth(BusWidth bus_width) override; + virtual BusWidth GetBusWidth() const override; + + virtual u32 GetDeviceClockFrequencyKHz() const override { + return this->device_clock_frequency_khz; + } + + virtual void SetPowerSaving(bool en) override; + virtual bool IsPowerSavingEnable() const override { + return this->is_power_saving_enable; + } + + virtual void EnableDeviceClock() override; + virtual void DisableDeviceClock() override; + virtual bool IsDeviceClockEnable() const override { + return this->is_device_clock_enable; + } + + virtual u32 GetMaxTransferNumBlocks() const override { + return SdHostStandardRegisters::BlockCountMax; + } + + virtual void ChangeCheckTransferInterval(u32 ms) override; + virtual void SetDefaultCheckTransferInterval() override; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; + virtual Result IssueStopTransmissionCommand(u32 *out_response) override; + + virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const override; + virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const override; + + virtual bool IsSupportedTuning() const override { + return false; + } + + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) { + AMS_UNUSED(speed_mode, command_index); + AMS_ABORT("Tuning not supported\n"); + } + + virtual void SaveTuningStatusForHs400() { + AMS_ABORT("SaveTuningStatusForHs400 not supported\n"); + } + + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp new file mode 100644 index 000000000..2e5a73eee --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::sdmmc::impl { + + struct SdHostStandardRegisters { + volatile uint32_t dma_address; + volatile uint16_t block_size; + volatile uint16_t block_count; + volatile uint32_t argument; + volatile uint16_t transfer_mode; + volatile uint16_t command; + volatile uint32_t response[0x4]; + volatile uint32_t buffer; + volatile uint32_t present_state; + volatile uint8_t host_control; + volatile uint8_t power_control; + volatile uint8_t block_gap_control; + volatile uint8_t wake_up_control; + volatile uint16_t clock_control; + volatile uint8_t timeout_control; + volatile uint8_t software_reset; + volatile uint16_t normal_int_status; + volatile uint16_t error_int_status; + volatile uint16_t normal_int_enable; + volatile uint16_t error_int_enable; + volatile uint16_t normal_signal_enable; + volatile uint16_t error_signal_enable; + volatile uint16_t acmd12_err; + volatile uint16_t host_control2; + volatile uint32_t capabilities; + volatile uint32_t capabilities_1; + volatile uint32_t max_current; + volatile uint32_t _0x4c; + volatile uint16_t set_acmd12_error; + volatile uint16_t set_int_error; + volatile uint8_t adma_error; + volatile uint8_t _0x56[0x3]; + volatile uint32_t adma_address; + volatile uint32_t upper_adma_address; + volatile uint16_t preset_for_init; + volatile uint16_t preset_for_default; + volatile uint16_t preset_for_high; + volatile uint16_t preset_for_sdr12; + volatile uint16_t preset_for_sdr25; + volatile uint16_t preset_for_sdr50; + volatile uint16_t preset_for_sdr104; + volatile uint16_t preset_for_ddr50; + volatile uint32_t _0x70[0x23]; + volatile uint16_t slot_int_status; + volatile uint16_t host_version; + + static constexpr inline u16 BlockCountMax = 0xFFFF; + }; + static_assert(sizeof(SdHostStandardRegisters) == 0x100); + + constexpr inline size_t SdmaBufferBoundary = 512_KB; + + #define SD_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (SD_HOST_STANDARD, NAME) + #define SD_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (SD_HOST_STANDARD, NAME, VALUE) + #define SD_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (SD_HOST_STANDARD, NAME, ENUM) + #define SD_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(SD_HOST_STANDARD, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_SD_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (SD_HOST_STANDARD, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_SD_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_SD_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_SD_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_SD_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + DEFINE_SD_REG(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, 0, 12); + DEFINE_SD_REG_THREE_BIT_ENUM(BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 12, FOUR_KB, EIGHT_KB, SIXTEEN_KB, THIRTY_TWO_KB, SIXTY_FOUR_KB, ONE_TWENTY_EIGHT_KB, TWO_FIFTY_SIX_KB, FIVE_TWELVE_KB); + constexpr inline size_t SdHostStandardBlockSizeTransferBlockSizeMax = 0xFFF; + + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DMA_ENABLE, 0, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_BLOCK_COUNT_ENABLE, 1, DISABLE, ENABLE); + DEFINE_SD_REG_TWO_BIT_ENUM(TRANSFER_MODE_AUTO_CMD_ENABLE, 2, DISABLE, CMD12_ENABLE, CMD23_ENABLE, AUTO_SELECT); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, 4, WRITE, READ); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_MULTI_BLOCK_SELECT, 5, SINGLE_BLOCK, MULTI_BLOCK); + + DEFINE_SD_REG_TWO_BIT_ENUM(COMMAND_RESPONSE_TYPE, 0, NO_RESPONSE, RESPONSE_LENGTH_136, RESPONSE_LENGTH_48, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_CRC_CHECK, 3, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_INDEX_CHECK, 4, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_DATA_PRESENT, 5, NO_DATA_PRESENT, DATA_PRESENT); + DEFINE_SD_REG(COMMAND_COMMAND_INDEX, 8, 6); + constexpr inline size_t SdHostStandardCommandIndexMax = 0x3F; + + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, 0, READY, NOT_READY); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, 1, READY, NOT_READY); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, 20, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT1_LINE_SIGNAL_LEVEL, 21, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT2_LINE_SIGNAL_LEVEL, 22, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT3_LINE_SIGNAL_LEVEL, 23, LOW, HIGH); + + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, 1, ONE_BIT, FOUR_BIT); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, 2, NORMAL_SPEED, HIGH_SPEED); + DEFINE_SD_REG_TWO_BIT_ENUM(HOST_CONTROL_DMA_SELECT, 3, SDMA, RESERVED1, ADMA2, ADMA2_OR_ADMA3); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, 5, USE_DATA_TRANSFER_WIDTH, EIGHT_BIT); + + DEFINE_SD_REG_BIT_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, 0, OFF, ON); + DEFINE_SD_REG_THREE_BIT_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1, RESERVED0, RESERVED1, RESERVED2, RESERVED3, RESERVED4, 1_8V, 3_0V, 3_3V); + + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, 2, DISABLE, ENABLE); + + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_ALL, 0, WORK, RESET); + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_CMD, 1, WORK, RESET); + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_DAT, 2, WORK, RESET); + + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, 0, NOT_COMPLETE, COMPLETE); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, 1, NOT_COMPLETE, COMPLETE); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, 3, NOT_GENERATED, GENERATED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, 15, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, 0, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, 1, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, 2, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, 3, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, 4, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, 5, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, 6, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, 8, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, 1, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, 2, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, 3, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, 4, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_ENABLE_COMMAND_COMPLETE, 0, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_ENABLE_TRANSFER_COMPLETE, 1, MASKED, ENABLED); + + #define SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_ENABLE_COMMAND_COMPLETE, __ENUM__), \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_ENABLE_TRANSFER_COMPLETE, __ENUM__) + + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_TIMEOUT_ERROR, 0, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_CRC_ERROR, 1, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_END_BIT_ERROR, 2, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_INDEX_ERROR, 3, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_TIMEOUT_ERROR, 4, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_CRC_ERROR, 5, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_END_BIT_ERROR, 6, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_AUTO_CMD_ERROR, 8, MASKED, ENABLED); + + #define SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_TIMEOUT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_CRC_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_END_BIT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_INDEX_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_TIMEOUT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_CRC_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_END_BIT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_AUTO_CMD_ERROR, __ENUM__) + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp new file mode 100644 index 000000000..1da622594 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_sd_host_standard_controller.hpp" +#include "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl { + + bool IsSocMariko(); + + constexpr inline size_t SdmmcRegistersSize = 0x200; + + class SdmmcController : public SdHostStandardController { + private: + struct SdmmcRegisters { + /* Standard registers. */ + volatile SdHostStandardRegisters sd_host_standard_registers; + + /* Vendor specific registers */ + volatile uint32_t vendor_clock_cntrl; + volatile uint32_t vendor_sys_sw_cntrl; + volatile uint32_t vendor_err_intr_status; + volatile uint32_t vendor_cap_overrides; + volatile uint32_t vendor_boot_cntrl; + volatile uint32_t vendor_boot_ack_timeout; + volatile uint32_t vendor_boot_dat_timeout; + volatile uint32_t vendor_debounce_count; + volatile uint32_t vendor_misc_cntrl; + volatile uint32_t max_current_override; + volatile uint32_t max_current_override_hi; + volatile uint32_t _0x12c[0x20]; + volatile uint32_t vendor_io_trim_cntrl; + + /* Start of sdmmc2/sdmmc4 only */ + volatile uint32_t vendor_dllcal_cfg; + volatile uint32_t vendor_dll_ctrl0; + volatile uint32_t vendor_dll_ctrl1; + volatile uint32_t vendor_dllcal_cfg_sta; + /* End of sdmmc2/sdmmc4 only */ + + volatile uint32_t vendor_tuning_cntrl0; + volatile uint32_t vendor_tuning_cntrl1; + volatile uint32_t vendor_tuning_status0; + volatile uint32_t vendor_tuning_status1; + volatile uint32_t vendor_clk_gate_hysteresis_count; + volatile uint32_t vendor_preset_val0; + volatile uint32_t vendor_preset_val1; + volatile uint32_t vendor_preset_val2; + volatile uint32_t sdmemcomppadctrl; + volatile uint32_t auto_cal_config; + volatile uint32_t auto_cal_interval; + volatile uint32_t auto_cal_status; + volatile uint32_t io_spare; + volatile uint32_t sdmmca_mccif_fifoctrl; + volatile uint32_t timeout_wcoal_sdmmca; + volatile uint32_t _0x1fc; + }; + static_assert(sizeof(SdmmcRegisters) == SdmmcRegistersSize); + private: + SdmmcRegisters *sdmmc_registers; + /* TODO */ + public: + explicit SdmmcController(dd::PhysicalAddress registers_phys_addr) : SdHostStandardController(registers_phys_addr, SdmmcRegistersSize) { + /* Set sdmmc registers. */ + static_assert(offsetof(SdmmcRegisters, sd_host_standard_registers) == 0); + this->sdmmc_registers = reinterpret_cast(this->registers); + } + + /* TODO */ + }; + + class Sdmmc2And4Controller : public SdmmcController { + /* TODO */ + public: + explicit Sdmmc2And4Controller(dd::PhysicalAddress registers_phys_addr) : SdmmcController(registers_phys_addr) { + /* ... */ + } + + /* TODO */ + }; + + constexpr inline dd::PhysicalAddress Sdmmc4RegistersPhysicalAddress = UINT64_C(0x700B0600); + + class Sdmmc4Controller : public Sdmmc2And4Controller { + /* TODO */ + public: + Sdmmc4Controller() : Sdmmc2And4Controller(Sdmmc4RegistersPhysicalAddress) { + /* ... */ + } + + /* TODO */ + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp new file mode 100644 index 000000000..635fc9d41 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" + + namespace ams::sdmmc::impl { + + using SdmmcControllerForMmc = Sdmmc4Controller; + + } + +#else + #error "Unknown board for ams::sdmmc::SdmmcController" +#endif diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp new file mode 100644 index 000000000..551cf342c --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp @@ -0,0 +1,86 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + namespace { + + #if defined(AMS_SDMMC_USE_OS_TIMER) + void SpinWaitMicroSeconds(u32 us) { + const os::Tick timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMicroSeconds(us)) + os::Tick(1); + while (true) { + if (os::GetSystemTick() > timeout_tick) { + break; + } + } + } + + ALWAYS_INLINE void DataSynchronizationBarrier() { + #if defined(ATMOSPHERE_ARCH_ARM64) + __asm__ __volatile__("dsb sy" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("dsb" ::: "memory"); + #else + #error "Unknown architecture for DataSynchronizationBarrier" + #endif + } + + ALWAYS_INLINE void InstructionSynchronizationBarrier() { + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("isb" ::: "memory"); + #else + #error "Unknown architecture for InstructionSynchronizationBarrier" + #endif + } + #endif + + } + + void WaitMicroSeconds(u32 us) { + #if defined(AMS_SDMMC_USE_OS_TIMER) + /* Ensure that nothing is reordered before we wait. */ + DataSynchronizationBarrier(); + InstructionSynchronizationBarrier(); + + /* If the time is small, spinloop, otherwise pend ourselves. */ + if (us < 100) { + SpinWaitMicroSeconds(us); + } else { + os::SleepThread(TimeSpan::FromMicroSeconds(us)); + } + + /* Ensure that nothing is reordered after we wait. */ + DataSynchronizationBarrier(); + InstructionSynchronizationBarrier(); + #elif defined(AMS_SDMMC_USE_UTIL_TIMER) + util::WaitMicroSeconds(us); + #else + #error "Unknown context for ams::sdmmc::impl::WaitMicroSeconds" + #endif + } + + void WaitClocks(u32 num_clocks, u32 clock_frequency_khz) { + AMS_ABORT_UNLESS(clock_frequency_khz > 0); + WaitMicroSeconds(util::DivideUp(1000 * num_clocks, clock_frequency_khz)); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp new file mode 100644 index 000000000..5d8149591 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::sdmmc::impl { + + void WaitMicroSeconds(u32 us); + void WaitClocks(u32 num_clocks, u32 clock_frequency_khz); + + #if defined(AMS_SDMMC_USE_OS_TIMER) + class ManualTimer { + private: + os::Tick timeout_tick; + bool is_timed_out; + public: + explicit ManualTimer(u32 ms) { + this->timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMilliSeconds(ms)); + this->is_timed_out = false; + } + + bool Update() { + if (this->is_timed_out) { + return false; + } + + this->is_timed_out = os::GetSystemTick() > this->timeout_tick; + return true; + } + }; + #elif defined(AMS_SDMMC_USE_UTIL_TIMER) + class ManualTimer { + private: + u32 timeout_us; + bool is_timed_out; + public: + explicit ManualTimer(u32 ms) { + this->timeout_us = util::GetMicroSeconds() + (ms * 1000); + this->is_timed_out = false; + } + + bool Update() { + if (this->is_timed_out) { + return false; + } + + this->is_timed_out = util::GetMicroSeconds() > this->timeout_us; + return true; + } + }; + #else + #error "Unknown context for ams::sdmmc::ManualTimer" + #endif + +}