sdmmc: add most of SdHostStandardController

This commit is contained in:
Michael Scire 2020-10-20 17:24:31 -07:00
parent 88b81e5fb0
commit 895b332a86
37 changed files with 2140 additions and 221 deletions

View file

@ -17,7 +17,6 @@
#include <vapours.hpp>
#include <exosphere/common.hpp>
#include <exosphere/reg.hpp>
#include <exosphere/hw.hpp>
#include <exosphere/util.hpp>
#include <exosphere/mmu.hpp>

View file

@ -15,7 +15,6 @@
*/
#pragma once
#include <vapours.hpp>
#include <exosphere/reg.hpp>
#define MC_INTSTATUS (0x000)
#define MC_INTMASK (0x004)

View file

@ -15,7 +15,6 @@
*/
#pragma once
#include <vapours.hpp>
#include <exosphere/reg.hpp>
#define APBDEV_PMC_CNTRL (0x000)
#define APBDEV_PMC_WAKE_MASK (0x00C)

View file

@ -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<typename T, typename U> requires std::integral<T> && std::integral<U>
constexpr T DivideUp(T x, U y) {
return (x + (y - 1)) / y;
}
}

View file

@ -60,7 +60,6 @@
#include <stratosphere/pgl.hpp>
#include <stratosphere/psc.hpp>
#include <stratosphere/pm.hpp>
#include <stratosphere/reg.hpp>
#include <stratosphere/ro.hpp>
#include <stratosphere/settings.hpp>
#include <stratosphere/sf.hpp>

View file

@ -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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
namespace ams::reg {
inline void Write(volatile u32 *reg, u32 val) {
*reg = val;
}
inline void Write(uintptr_t reg, u32 val) {
Write(reinterpret_cast<volatile u32 *>(reg), val);
}
inline u32 Read(volatile u32 *reg) {
return *reg;
}
inline u32 Read(uintptr_t reg) {
return Read(reinterpret_cast<volatile u32 *>(reg));
}
inline void SetBits(volatile u32 *reg, u32 mask) {
*reg = *reg | mask;
}
inline void SetBits(uintptr_t reg, u32 mask) {
SetBits(reinterpret_cast<volatile u32 *>(reg), mask);
}
inline void ClearBits(volatile u32 *reg, u32 mask) {
*reg = *reg & ~mask;
}
inline void ClearBits(uintptr_t reg, u32 mask) {
ClearBits(reinterpret_cast<volatile u32 *>(reg), mask);
}
inline void MaskBits(volatile u32 *reg, u32 mask) {
*reg = *reg & mask;
}
inline void MaskBits(uintptr_t reg, u32 mask) {
MaskBits(reinterpret_cast<volatile u32 *>(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<volatile u32 *>(reg), val, mask);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
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, &region_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<uintptr_t>(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);
}
}

View file

@ -25,6 +25,7 @@
#include <vapours/util.hpp>
#include <vapours/results.hpp>
#include <vapours/reg.hpp>
#include <vapours/crypto.hpp>
#include <vapours/svc.hpp>

View file

@ -19,4 +19,6 @@
#include <vapours/assert.hpp>
#include <vapours/results.hpp>
#include <vapours/dd/dd_device_virtual_address.hpp>
#include <vapours/dd/dd_common_types.hpp>
#include <vapours/dd/dd_io_mapping.hpp>
#include <vapours/dd/dd_cache.hpp>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours/dd/dd_common_types.hpp>
namespace ams::dd {
void InvalidateDataCache(void *addr, size_t size);
void StoreDataCache(void *addr, size_t size);
void FlushDataCache(void *addr, size_t size);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours/common.hpp>
#include <vapours/assert.hpp>
#include <vapours/results.hpp>
namespace ams::dd {
using PhysicalAddress = u64;
using DeviceVirtualAddress = u64;
static_assert(std::same_as<PhysicalAddress, ams::svc::PhysicalAddress>);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours/dd/dd_common_types.hpp>
namespace ams::dd {
uintptr_t QueryIoMapping(dd::PhysicalAddress phys_addr, size_t size);
}

View file

@ -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 &reg, u32 val) { reg = val; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void Write(volatile IntType *reg, std::type_identity_t<IntType> val) { *reg = val; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void Write(volatile IntType &reg, std::type_identity_t<IntType> val) { reg = val; }
ALWAYS_INLINE void Write(uintptr_t reg, u32 val) { Write(reinterpret_cast<volatile u32 *>(reg), val); }
template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void Write(volatile u32 *reg, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); }
template<typename IntType, typename... Values> requires std::unsigned_integral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void Write(volatile IntType *reg, const Values... values) { return Write(reg, static_cast<IntType>((EncodeValue(values) | ...))); }
template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void Write(volatile u32 &reg, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); }
template<typename IntType, typename... Values> requires std::unsigned_integral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void Write(volatile IntType &reg, const Values... values) { return Write(reg, static_cast<IntType>((EncodeValue(values) | ...))); }
template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::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 &reg) { return reg; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE IntType Read(volatile IntType *reg) { return *reg; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE IntType Read(volatile IntType &reg) { return reg; }
ALWAYS_INLINE u32 Read(uintptr_t reg) { return Read(reinterpret_cast<volatile u32 *>(reg)); }
ALWAYS_INLINE u32 Read(volatile u32 *reg, u32 mask) { return *reg & mask; }
ALWAYS_INLINE u32 Read(volatile u32 &reg, u32 mask) { return reg & mask; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE IntType Read(volatile IntType *reg, std::type_identity_t<IntType> mask) { return *reg & mask; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE IntType Read(volatile IntType &reg, std::type_identity_t<IntType> mask) { return reg & mask; }
ALWAYS_INLINE u32 Read(uintptr_t reg, u32 mask) { return Read(reinterpret_cast<volatile u32 *>(reg), mask); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE u32 Read(volatile u32 *reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); }
template<typename IntType, typename... Masks> requires std::unsigned_integral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE IntType Read(volatile IntType *reg, const Masks... masks) { return Read(reg, static_cast<IntType>((EncodeMask(masks) | ...))); }
template<typename IntType, typename... Masks> requires std::unsigned_integral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE IntType Read(volatile IntType &reg, const Masks... masks) { return Read(reg, static_cast<IntType>((EncodeMask(masks) | ...))); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE u32 Read(volatile u32 &reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); }
ALWAYS_INLINE u32 Read(uintptr_t reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE u32 Read(uintptr_t reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); }
template<typename IntType, typename... Values> requires std::unsigned_integral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE bool HasValue(volatile IntType *reg, const Values... values) { return Read(reg, static_cast<IntType>((EncodeMask(values) | ...))) == static_cast<IntType>(Encode(values...)); }
template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE bool HasValue(volatile u32 *reg, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); }
template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE bool HasValue(volatile u32 &reg, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); }
template<typename IntType, typename... Values> requires std::unsigned_integral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE bool HasValue(volatile IntType &reg, const Values... values) { return Read(reg, static_cast<IntType>((EncodeMask(values) | ...))) == static_cast<IntType>(Encode(values...)); }
template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::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 &reg, 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<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE IntType GetValue(volatile IntType *reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE IntType GetValue(volatile IntType &reg, 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<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void ReadWrite(volatile IntType *reg, std::type_identity_t<IntType> val, std::type_identity_t<IntType> mask) { *reg = (*reg & (~mask)) | (val & mask); }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void ReadWrite(volatile IntType &reg, std::type_identity_t<IntType> val, std::type_identity_t<IntType> 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 &reg, u32 val, u32 mask) { reg = ( reg & (~mask)) | (val & mask); }
ALWAYS_INLINE void ReadWrite(uintptr_t reg, u32 val, u32 mask) { ReadWrite(reinterpret_cast<volatile u32 *>(reg), val, mask); }
template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void ReadWrite(volatile u32 *reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); }
template<typename IntType, typename... Values> requires std::unsigned_integral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void ReadWrite(volatile IntType *reg, const Values... values) { return ReadWrite(reg, static_cast<IntType>((EncodeValue(values) | ...)), static_cast<IntType>((EncodeMask(values) | ...))); }
template<typename IntType, typename... Values> requires std::unsigned_integral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void ReadWrite(volatile IntType &reg, const Values... values) { return ReadWrite(reg, static_cast<IntType>((EncodeValue(values) | ...)), static_cast<IntType>((EncodeMask(values) | ...))); }
template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void ReadWrite(volatile u32 &reg, 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<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...))
ALWAYS_INLINE void ReadWrite(uintptr_t reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void SetBits(volatile IntType *reg, std::type_identity_t<IntType> mask) { *reg = *reg | mask; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void SetBits(volatile IntType &reg, std::type_identity_t<IntType> mask) { reg = reg | mask; }
ALWAYS_INLINE void SetBits(volatile u32 *reg, u32 mask) { *reg = *reg | mask; }
ALWAYS_INLINE void SetBits(volatile u32 &reg, u32 mask) { reg = reg | mask; }
ALWAYS_INLINE void SetBits(uintptr_t reg, u32 mask) { SetBits(reinterpret_cast<volatile u32 *>(reg), mask); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void SetBits(volatile u32 *reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); }
template<typename IntType, typename... Masks> requires std::unsigned_integral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void SetBits(volatile IntType *reg, const Masks... masks) { return SetBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); }
template<typename IntType, typename... Masks> requires std::unsigned_integral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void SetBits(volatile IntType &reg, const Masks... masks) { return SetBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void SetBits(volatile u32 &reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); }
ALWAYS_INLINE void SetBits(uintptr_t reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void SetBits(uintptr_t reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void ClearBits(volatile IntType *reg, std::type_identity_t<IntType> mask) { *reg = *reg & ~mask; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void ClearBits(volatile IntType &reg, std::type_identity_t<IntType> mask) { reg = reg & ~mask; }
ALWAYS_INLINE void ClearBits(volatile u32 *reg, u32 mask) { *reg = *reg & ~mask; }
ALWAYS_INLINE void ClearBits(volatile u32 &reg, u32 mask) { reg = reg & ~mask; }
ALWAYS_INLINE void ClearBits(uintptr_t reg, u32 mask) { ClearBits(reinterpret_cast<volatile u32 *>(reg), mask); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void ClearBits(volatile u32 *reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); }
template<typename IntType, typename... Masks> requires std::unsigned_integral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void ClearBits(volatile IntType *reg, const Masks... masks) { return ClearBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); }
template<typename IntType, typename... Masks> requires std::unsigned_integral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void ClearBits(volatile IntType &reg, const Masks... masks) { return ClearBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void ClearBits(volatile u32 &reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); }
ALWAYS_INLINE void ClearBits(uintptr_t reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void ClearBits(uintptr_t reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void MaskBits(volatile IntType *reg, std::type_identity_t<IntType> mask) { *reg = *reg & mask; }
template<typename IntType> requires std::unsigned_integral<IntType>
ALWAYS_INLINE void MaskBits(volatile IntType &reg, std::type_identity_t<IntType> mask) { reg = reg & mask; }
ALWAYS_INLINE void MaskBits(volatile u32 *reg, u32 mask) { *reg = *reg & mask; }
ALWAYS_INLINE void MaskBits(volatile u32 &reg, u32 mask) { reg = reg & mask; }
ALWAYS_INLINE void MaskBits(uintptr_t reg, u32 mask) { MaskBits(reinterpret_cast<volatile u32 *>(reg), mask); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void MaskBits(volatile u32 *reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); }
template<typename IntType, typename... Masks> requires std::unsigned_integral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void MaskBits(volatile IntType *reg, const Masks... masks) { return MaskBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); }
template<typename IntType, typename... Masks> requires std::unsigned_integral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void MaskBits(volatile IntType &reg, const Masks... masks) { return MaskBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...))
ALWAYS_INLINE void MaskBits(volatile u32 &reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); }
template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::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}

View file

@ -45,6 +45,7 @@
#include <vapours/results/pm_results.hpp>
#include <vapours/results/psc_results.hpp>
#include <vapours/results/ro_results.hpp>
#include <vapours/results/sdmmc_results.hpp>
#include <vapours/results/settings_results.hpp>
#include <vapours/results/sf_results.hpp>
#include <vapours/results/sm_results.hpp>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours/results/results_common.hpp>
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);
}

View file

@ -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
#endif

View file

@ -17,7 +17,6 @@
#pragma once
#include <vapours/sdmmc/sdmmc_build_config.hpp>
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);
}

View file

@ -34,6 +34,7 @@
#include <vapours/util/util_intrusive_list.hpp>
#include <vapours/util/util_intrusive_red_black_tree.hpp>
#include <vapours/util/util_tinymt.hpp>
#include <vapours/util/util_timer.hpp>
#include <vapours/util/util_uuid.hpp>
#include <vapours/util/util_bounded_map.hpp>
#include <vapours/util/util_overlap.hpp>

View file

@ -73,4 +73,9 @@ namespace ams::util {
return IsAligned(reinterpret_cast<uintptr_t>(value), alignment);
}
}
template<typename T, typename U> requires std::integral<T> && std::integral<U>
constexpr ALWAYS_INLINE T DivideUp(T x, U y) {
return (x + (y - 1)) / y;
}
}

View file

@ -17,10 +17,10 @@
#pragma once
#include <vapours/common.hpp>
#include <vapours/assert.hpp>
#include <vapours/results.hpp>
namespace ams::dd {
namespace ams::util {
using DeviceVirtualAddress = u64;
u32 GetMicroSeconds();
void WaitMicroSeconds(int us);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#if defined(ATMOSPHERE_IS_STRATOSPHERE)
#include <stratosphere.hpp>
#else
#include <vapours.hpp>
#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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#if defined(ATMOSPHERE_IS_STRATOSPHERE)
#include <stratosphere.hpp>
#else
#include <vapours.hpp>
#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<uintptr_t>(phys_addr);
#elif defined(ATMOSPHERE_ARCH_ARM)
/* TODO: BPMP translation? */
AMS_UNUSED(size);
return static_cast<uintptr_t>(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<uintptr_t>(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, &region_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<uintptr_t>(virt_addr) + offset;
#else
#error "Unknown execution context for ams::dd::QueryIoMapping!"
#endif
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#if defined(ATMOSPHERE_IS_STRATOSPHERE)
#include <stratosphere.hpp>
#else
#include <vapours.hpp>
#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<uintptr_t>(addr) & ~(cache_line_size - 1);
const uintptr_t end_addr = reinterpret_cast<uintptr_t>(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<uintptr_t>(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<uintptr_t>(addr) & ~(cache_line_size - 1);
const uintptr_t end_addr = reinterpret_cast<uintptr_t>(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<uintptr_t>(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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#if defined(ATMOSPHERE_OS_HORIZON)
#include "dd_cache_impl.os.horizon.hpp"
#else
#error "Unknown OS for ams::dd::CacheImpl"
#endif

View file

@ -14,7 +14,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
namespace ams::sdmmc::impl::ClockResetController {

View file

@ -14,7 +14,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#include "sdmmc_i_host_controller.hpp"

View file

@ -14,7 +14,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#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;
};
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <vapours.hpp>
#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);
}
}

View file

@ -14,7 +14,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#include "sdmmc_i_host_controller.hpp"
#include "sdmmc_i_device_accessor.hpp"

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <vapours.hpp>
#include "sdmmc_sd_host_standard_controller.hpp"
#include "sdmmc_timer.hpp"
#if defined(ATMOSPHERE_IS_STRATOSPHERE)
#include <stratosphere/dd.hpp>
#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<u16>(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<uintptr_t>(xfer_data->buffer), xfer_data->block_size * num_blocks);
const u16 num_xfer_blocks = num_blocks;
#else
const u64 address = reinterpret_cast<uintptr_t>(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<u32>(address >> 0));
reg::Write(this->registers->upper_adma_address, static_cast<u32>(address >> BITSIZEOF(u32)));
/* Set our next sdma address. */
this->next_sdma_address = util::AlignDown<u64>(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<u16>(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<u32>(this->next_sdma_address >> 0));
reg::Write(this->registers->upper_adma_address, static_cast<u32>(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<u32>(this->next_sdma_address >> 0));
reg::Write(this->registers->upper_adma_address, static_cast<u32>(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<uintptr_t>(registers_phys_addr);
#endif
/* Set registers. */
AMS_ABORT_UNLESS(registers_addr != 0);
this->registers = reinterpret_cast<SdHostStandardRegisters *>(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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#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");
}
};
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
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__)
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#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<SdmmcRegisters *>(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 */
};
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#if defined(ATMOSPHERE_IS_STRATOSPHERE)
#include <stratosphere.hpp>
#else
#include <vapours.hpp>
#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));
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
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
}