mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 20:31:14 +00:00
sdmmc: add most of SdHostStandardController
This commit is contained in:
parent
88b81e5fb0
commit
895b332a86
37 changed files with 2140 additions and 221 deletions
|
@ -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>
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <exosphere/reg.hpp>
|
||||
|
||||
#define MC_INTSTATUS (0x000)
|
||||
#define MC_INTMASK (0x004)
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <exosphere/reg.hpp>
|
||||
|
||||
#define APBDEV_PMC_CNTRL (0x000)
|
||||
#define APBDEV_PMC_WAKE_MASK (0x00C)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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, ®ion_size, aligned_addr, aligned_size)) {
|
||||
/* Official software handles this by returning 0. */
|
||||
R_CATCH(svc::ResultNotFound) { return 0; }
|
||||
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
|
||||
AMS_ASSERT(region_size >= aligned_size);
|
||||
} else {
|
||||
R_TRY_CATCH(svcLegacyQueryIoMapping(&virtual_addr, aligned_addr, aligned_size)) {
|
||||
/* Official software handles this by returning 0. */
|
||||
R_CATCH(svc::ResultNotFound) { return 0; }
|
||||
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
|
||||
}
|
||||
|
||||
return static_cast<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);
|
||||
}
|
||||
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
#include <vapours/crypto.hpp>
|
||||
#include <vapours/svc.hpp>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
25
libraries/libvapours/include/vapours/dd/dd_cache.hpp
Normal file
25
libraries/libvapours/include/vapours/dd/dd_cache.hpp
Normal 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);
|
||||
|
||||
}
|
29
libraries/libvapours/include/vapours/dd/dd_common_types.hpp
Normal file
29
libraries/libvapours/include/vapours/dd/dd_common_types.hpp
Normal 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>);
|
||||
|
||||
}
|
23
libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp
Normal file
23
libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp
Normal 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);
|
||||
|
||||
}
|
|
@ -48,100 +48,132 @@ namespace ams::reg {
|
|||
return (EncodeValue(values) | ...);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void Write(volatile u32 *reg, u32 val) { *reg = val; }
|
||||
ALWAYS_INLINE void Write(volatile u32 ®, u32 val) { reg = val; }
|
||||
template<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 ®, 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 ®, 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 ®, 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 ®) { 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 ®) { 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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 ®, 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}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -27,18 +27,24 @@
|
|||
//#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!"
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
37
libraries/libvapours/source/dd/dd_cache.cpp
Normal file
37
libraries/libvapours/source/dd/dd_cache.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
69
libraries/libvapours/source/dd/dd_io_mapping.cpp
Normal file
69
libraries/libvapours/source/dd/dd_io_mapping.cpp
Normal 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, ®ion_size, aligned_addr, aligned_size)) {
|
||||
/* Official software handles this by returning 0. */
|
||||
R_CATCH(svc::ResultNotFound) { return 0; }
|
||||
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
|
||||
AMS_ASSERT(region_size >= aligned_size);
|
||||
} else {
|
||||
R_TRY_CATCH(svc::LegacyQueryIoMapping(&virt_addr, aligned_addr, aligned_size)) {
|
||||
/* Official software handles this by returning 0. */
|
||||
R_CATCH(svc::ResultNotFound) { return 0; }
|
||||
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
|
||||
}
|
||||
|
||||
return static_cast<uintptr_t>(virt_addr) + offset;
|
||||
|
||||
#else
|
||||
#error "Unknown execution context for ams::dd::QueryIoMapping!"
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
22
libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp
Normal file
22
libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp
Normal 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
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
33
libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp
Normal file
33
libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
|
@ -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__)
|
||||
|
||||
}
|
|
@ -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 */
|
||||
};
|
||||
|
||||
}
|
|
@ -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
|
86
libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp
Normal file
86
libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
68
libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp
Normal file
68
libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp
Normal 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
|
||||
|
||||
}
|
Loading…
Reference in a new issue