diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e717de0..dddab13 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -19,6 +19,7 @@ jobs: "ACE3DS", "AK2", "AKRPG", + "DATEL", "DSPICO", "DSTT", "EZP", diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec2354b..ef89cf6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,7 @@ jobs: "ACE3DS", "AK2", "AKRPG", + "DATEL", "DSPICO", "DSTT", "EZP", diff --git a/README.md b/README.md index 7d8b496..b4d8b47 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Note that there can be some game compatibility differences between different pla | ACE3DS | Ace3DS+, Gateway 3DS (blue), r4isdhc.com.cn carts, r4isdhc.hk carts 2020+, various derivatives | ✅ | | AK2 | Acekard 2, 2.1, 2i, r4ids.cn, various derivatives | ❌ | | AKRPG | Acekard RPG SD card | ❌ | +| DATEL | DATEL devices consisting of GAMES n' MUSIC and Action Replay DS(i) Media Edition | ❌ | | DSPICO | DSpico | ✅ | | DSTT | DSTT, SuperCard DSONE SDHC, r4isdhc.com carts 2014+, r4i-sdhc.com carts, various derivatives | ❌ | | EZP | EZ-Flash Parallel | ❌ | diff --git a/arm9/source/patches/platform/LoaderPlatformFactory.cpp b/arm9/source/patches/platform/LoaderPlatformFactory.cpp index 852ac76..53c2f0a 100644 --- a/arm9/source/patches/platform/LoaderPlatformFactory.cpp +++ b/arm9/source/patches/platform/LoaderPlatformFactory.cpp @@ -13,6 +13,7 @@ #include "patches/platform/r4idsn/R4iDSNLoaderPlatform.h" #include "patches/platform/supercard/SuperCardLoaderPlatform.h" #include "patches/platform/ezp/EZPLoaderPlatform.h" +#include "patches/platform/datel/DatelLoaderPlatform.h" #include "LoaderPlatformFactory.h" LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const @@ -43,6 +44,8 @@ LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const return new SuperCardLoaderPlatform(); #elif defined(PICO_LOADER_TARGET_EZP) return new EZPLoaderPlatform(); +#elif defined(PICO_LOADER_TARGET_DATEL) + return new DatelLoaderPlatform(); #else #error "No loader platform defined" return nullptr; diff --git a/arm9/source/patches/platform/SdioDefinitions.h b/arm9/source/patches/platform/SdioDefinitions.h index f25b076..985df45 100644 --- a/arm9/source/patches/platform/SdioDefinitions.h +++ b/arm9/source/patches/platform/SdioDefinitions.h @@ -12,6 +12,8 @@ #define SD_CMD16_SET_BLOCKLEN 16 #define SD_CMD55_APP_CMD 55 +#define SD_SPI_CMD58_READ_OCR 58 + #define SD_ACMD6_SET_BUS_WIDTH 6 #define SD_ACMD41_SD_SEND_OP_COND 41 diff --git a/arm9/source/patches/platform/datel/DatelLoaderPlatform.cpp b/arm9/source/patches/platform/datel/DatelLoaderPlatform.cpp new file mode 100644 index 0000000..4ee3402 --- /dev/null +++ b/arm9/source/patches/platform/datel/DatelLoaderPlatform.cpp @@ -0,0 +1,154 @@ +#include "common.h" +#include +#include "../SdioDefinitions.h" +#include "thumbInstructions.h" +#include "DatelLoaderPlatform.h" + +static constexpr int MAX_STARTUP_TRIES = 5000; +static constexpr int SD_COMMAND_TIMEOUT = 0xFFF; +static constexpr u32 DATEL_CTRL_BASE = (MCCNT1_RESET_OFF | MCCNT1_CMD_SCRAMBLE | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_LATENCY2(0x3F)); + +static constexpr u8 DATEL_CMD_F2_SPI_ENABLE = 0xCC; +static constexpr u8 DATEL_CMD_F2_SPI_DISABLE = 0xC8; + +static inline u64 DATEL_CMD_F2(u32 param1, u8 param2) +{ + return (0xF200000000000000ull | ((u64)param1 << 24) | ((u64)param2 << 16)); +} + +static inline void enableSpi() +{ + REG_MCCNT0 = MCCNT0_MODE_SPI | MCCNT0_SPI_HOLD_CS | MCCNT0_ENABLE; +} + +static u8 readWriteSpiByte(u8 data) +{ + REG_MCD0 = data; + while(REG_MCCNT0 & MCCNT0_SPI_BUSY); + return REG_MCD0; +} + +static u8 readSpiByteTimeout() +{ + auto timeout = SD_COMMAND_TIMEOUT; + u8 res; + do + { + res = readWriteSpiByte(0xFF); + } while (res == 0xFF && --timeout > 0); + return res; +} + +static void sendNtrCommandF2(u32 param1, u8 param2) +{ + card_romSetCmd(DATEL_CMD_F2(param1, param2)); + card_romStartXfer(DATEL_CTRL_BASE | MCCNT1_LEN_0, false); + card_romWaitBusy(); +} + +static void cycleSpi() +{ + sendNtrCommandF2(0, DATEL_CMD_F2_SPI_DISABLE); + enableSpi(); + readWriteSpiByte(0xFF); + sendNtrCommandF2(0, DATEL_CMD_F2_SPI_ENABLE); + enableSpi(); +} + +// Sends SDIO command to DATEL device. +static u8 spiSendSdioCommand(u8 cmdId, u32 arg, u8 * buffer, int messageLen) +{ + cycleSpi(); + u8 cmd[6]; + + // Build a SPI SD command to be sent as-is. + cmd[0] = 0x40 | (cmdId & 0x3f); + cmd[1] = arg >> 24; + cmd[2] = arg >> 16; + cmd[3] = arg >> 8; + cmd[4] = arg >> 0; + // CRC in SPI mode is ignored for every command but CMD0 (hardcoded to 0x95) + // and CMD8, hardcoded to 0x86 with the default 0x1AA argument. + cmd[5] = (messageLen > 1) ? 0x86 : 0x95; + + for (u8 byte : cmd) + { + readWriteSpiByte(byte); + } + + u8 timeout = readSpiByteTimeout(); + + for (int i = 0; i < (messageLen - 1); i++) + { + buffer[i] = readWriteSpiByte(0xFF); + } + + return timeout; +} + +static u8 spiSendSdioCommandR0(u8 cmd, u32 arg) +{ + return spiSendSdioCommand(cmd, arg, nullptr, 1); +} + +static bool trySendAcmd41(u32 acmd41Arg) +{ + for (int i = 0; i < MAX_STARTUP_TRIES; i++) + { + // Send ACMD41. + spiSendSdioCommandR0(SD_CMD55_APP_CMD, 0); + if (spiSendSdioCommandR0(SD_ACMD41_SD_SEND_OP_COND, acmd41Arg) == 0) + { + return true; + } + } + return false; +} + +bool DatelLoaderPlatform::InitializeSdCard() +{ + for (int i = 0; i < 0x100; i++) + { + sendNtrCommandF2(0x7FFFFFFF | ((i & 1) << 31), 0x00); + } + + // Send CMD0. + if (spiSendSdioCommandR0(SD_CMD0_GO_IDLE_STATE, 0) != 0x01) + { + return false; + } + + u32 cmd8Answer = 0; + + u32 acmd41Arg = 0; + bool isV2 = false; + + if (spiSendSdioCommand(SD_CMD8_SEND_IF_COND, SD_IF_COND_PATTERN, (u8*)&cmd8Answer, 5) == 0x1 + && cmd8Answer == 0xAA010000) + { + isV2 = true; + acmd41Arg |= (1 << 30); // Set HCS bit,Supports SDHC + } + + if (!trySendAcmd41(acmd41Arg)) + { + return false; + } + + bool isSdhc = false; + if (isV2) + { + u32 cmd58Answer = 0; + spiSendSdioCommand(SD_SPI_CMD58_READ_OCR, 0, (u8*)&cmd58Answer, 5); + isSdhc = (cmd58Answer & 0x40) != 0; + } + spiSendSdioCommandR0(SD_CMD16_SET_BLOCKLEN, 0x200); + + const u16 nonSdhcOpcode = THUMB_LSLS_IMM(THUMB_R0, THUMB_R0, 9); + const u16 sdhcOpcode = THUMB_MOVS_REG(THUMB_R0, THUMB_R0); + const u16 opcode = isSdhc ? sdhcOpcode : nonSdhcOpcode; + datel_writeSectorSdhcLabel = opcode; + datel_readSectorSdhcLabel = opcode; + + return true; +} diff --git a/arm9/source/patches/platform/datel/DatelLoaderPlatform.h b/arm9/source/patches/platform/datel/DatelLoaderPlatform.h new file mode 100644 index 0000000..0c6bed1 --- /dev/null +++ b/arm9/source/patches/platform/datel/DatelLoaderPlatform.h @@ -0,0 +1,49 @@ +#pragma once +#include "common.h" +#include "../LoaderPlatform.h" +#include "DatelSpiCommandsAsm.h" +#include "DatelReadSectorsAsm.h" +#include "DatelWriteSectorsAsm.h" + +/// @brief Implementation of LoaderPlatform for the DATEL line of flashcarts +class DatelLoaderPlatform : public LoaderPlatform +{ +public: + const SdReadPatchCode* CreateSdReadPatchCode( + PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override + { + auto spi = patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new DatelReadSpiBytePatchCode(patchHeap); + }); + auto sendSdio = patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new DatelSendSDIOCommandPatchCode(patchHeap, spi); + }); + return patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new DatelReadSdPatchCode(patchHeap, spi, sendSdio); + }); + } + + const SdWritePatchCode* CreateSdWritePatchCode( + PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override + { + auto spi = patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new DatelReadSpiBytePatchCode(patchHeap); + }); + auto sendSdio = patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new DatelSendSDIOCommandPatchCode(patchHeap, spi); + }); + return patchCodeCollection.GetOrAddSharedPatchCode([&] + { + return new DatelWriteSdPatchCode(patchHeap, spi, sendSdio); + }); + } + + LoaderPlatformType GetPlatformType() const override { return LoaderPlatformType::Slot1; } + + bool InitializeSdCard() override; +}; diff --git a/arm9/source/patches/platform/datel/DatelReadSectorsAsm.h b/arm9/source/patches/platform/datel/DatelReadSectorsAsm.h new file mode 100644 index 0000000..5e09a40 --- /dev/null +++ b/arm9/source/patches/platform/datel/DatelReadSectorsAsm.h @@ -0,0 +1,33 @@ +#pragma once +#include "sections.h" +#include "../SdReadPatchCode.h" +DEFINE_SECTION_SYMBOLS(datel_read); + +extern u16 datel_readSectorSdhcLabel; + +extern u32 datel_SDReadMultipleSector_SpiSendSDIOCommandR0; + +extern u32 datel_SDReadMultipleSector_ReadSpiByteTimeout; +extern u32 datel_SDReadMultipleSector_ReadSpiByte; + +extern "C" void datel_SDReadMultipleSector(u32 srcSector, void* dst, u32 sectorCount); + +class DatelReadSdPatchCode : public SdReadPatchCode +{ +public: + DatelReadSdPatchCode(PatchHeap& patchHeap, + const DatelReadSpiBytePatchCode* datelReadSpiBytePatchCode, + const DatelSendSDIOCommandPatchCode* datelSendSDIOCommandPatchCode) + : SdReadPatchCode(SECTION_START(datel_read), SECTION_SIZE(datel_read), patchHeap) + { + datel_SDReadMultipleSector_SpiSendSDIOCommandR0 = (u32)datelSendSDIOCommandPatchCode->GetSpiSendSDIOCommandR0Function(); + + datel_SDReadMultipleSector_ReadSpiByteTimeout = (u32)datelReadSpiBytePatchCode->GetReadSpiByteTimeoutFunction(); + datel_SDReadMultipleSector_ReadSpiByte = (u32)datelReadSpiBytePatchCode->GetReadSpiByteFunction(); + } + + const SdReadFunc GetSdReadFunction() const override + { + return (const SdReadFunc)GetAddressAtTarget((void*)datel_SDReadMultipleSector); + } +}; diff --git a/arm9/source/patches/platform/datel/DatelReadSectorsAsm.s b/arm9/source/patches/platform/datel/DatelReadSectorsAsm.s new file mode 100644 index 0000000..9bbdc03 --- /dev/null +++ b/arm9/source/patches/platform/datel/DatelReadSectorsAsm.s @@ -0,0 +1,102 @@ +#include "asminc.h" + +.syntax unified +.thumb + +.section "datel_read", "ax" + +.global datel_readSectorSdhcLabel + +@ All the called functions leave every registers unchanged, except for r0 in case the function has a return value + +@datel_SDReadMultipleSector(u32 sector, u8 * buffer, u32 num_sectors) +BEGIN_ASM_FUNC datel_SDReadMultipleSector + push {r3-r7, lr} + movs r4, r1 + @ We get the number of bytes total to write, which we'll compare to against in the main loop + lsls r3, r2, #9 + + @ Total written bytes + movs r5, #0 + +datel_readSectorSdhcLabel: + @ if not sdhc this needs to be shifted to the left by 9 + lsls r0, #9 + @mov r0, r0 + + movs r1, DATEL_SDIO_CMD18_READ_MULTIPLE_BLOCK + + ldr r7, datel_SDReadMultipleSector_SpiSendSDIOCommandR0 + bl datel_SDReadMultipleSector_Interwork + bne CMD18_not_ok + +read_next_sector: + ldr r7, datel_SDReadMultipleSector_ReadSpiByteTimeout + bl datel_SDReadMultipleSector_Interwork + + cmp r0, DATEL_SPI_START_DATA_TOKEN + bne wrong_spi_start_token + + @ preload datel_readSpiByte + ldr r7, datel_SDReadMultipleSector_ReadSpiByte +read_next_byte: + bl datel_SDReadMultipleSector_Interwork + strb r0, [r4, r5] + + adds r5, #1 + @ Shifting left by 0x17 will set the Zero flag if the number that was shifted is a multiple + @ of 0x200 (indicating a full sector has been written) + lsls r0, r5, #0x17 + bne read_next_byte + + @ drop crc + bl datel_SDReadMultipleSector_Interwork + bl datel_SDReadMultipleSector_Interwork + + cmp r3, r5 + bne read_next_sector + + movs r0, #0 + movs r1, DATEL_SDIO_CMD12_STOP_TRANSMISSION + movs r2, #7 + + @ datel_spiSendSDIOCommand is 1 instruction after datel_spiSendSDIOCommandR0 + ldr r7, datel_SDReadMultipleSector_SpiSendSDIOCommandR0 + adds r7, #2 + bl datel_SDReadMultipleSector_Interwork + + ldr r4, =DATEL_SD_CMD_TIMEOUT_LEN + ldr r7, datel_SDReadMultipleSector_ReadSpiByte +1: + bl datel_SDReadMultipleSector_Interwork + bne read_timeout_expired + subs r4, #1 + bne 1b + +read_timeout_expired: + @ movs r0, r4 + @ subs r3, r0, #1 + @ sbcs r0, r3 + +@ pico loader has no error result + @ pop {r3-r7, pc} + +CMD18_not_ok: +wrong_spi_start_token: + @ movs r0, #0 + pop {r3-r7, pc} + +datel_SDReadMultipleSector_Interwork: + bx r7 +.balign 4 +.pool + +.global datel_SDReadMultipleSector_SpiSendSDIOCommandR0 +datel_SDReadMultipleSector_SpiSendSDIOCommandR0: + .word 0 +.global datel_SDReadMultipleSector_ReadSpiByteTimeout +datel_SDReadMultipleSector_ReadSpiByteTimeout: + .word 0 +.global datel_SDReadMultipleSector_ReadSpiByte +datel_SDReadMultipleSector_ReadSpiByte: + .word 0 diff --git a/arm9/source/patches/platform/datel/DatelSpiCommandsAsm.h b/arm9/source/patches/platform/datel/DatelSpiCommandsAsm.h new file mode 100644 index 0000000..51d609d --- /dev/null +++ b/arm9/source/patches/platform/datel/DatelSpiCommandsAsm.h @@ -0,0 +1,66 @@ +#pragma once +#include "sections.h" +#include "../SdReadDmaPatchCode.h" + +DEFINE_SECTION_SYMBOLS(datel_read_spi); +DEFINE_SECTION_SYMBOLS(datel_spi_send); + +extern "C" u8 datel_readSpiByte(); +extern "C" u8 datel_readWriteSpiByte(u8 value); +extern "C" u8 datel_readSpiByteTimeout(); +extern "C" bool datel_waitSpiByteTimeout(); + +extern "C" u8 datel_spiSendSDIOCommandR0(u32 arg, u8 cmd); +extern "C" u8 datel_spiSendSDIOCommand(u32 arg, u8 cmd, int extraBytes); + +extern u32 datel_spiSendSDIOCommandR0_ReadWriteSpiByte; +extern u32 datel_spiSendSDIOCommandR0_ReadSpiByteTimeout; + +class DatelReadSpiBytePatchCode : public PatchCode +{ +public: + explicit DatelReadSpiBytePatchCode(PatchHeap& patchHeap) + : PatchCode(SECTION_START(datel_read_spi), SECTION_SIZE(datel_read_spi), patchHeap) { } + + const void* GetReadSpiByteFunction() const + { + return GetAddressAtTarget((void*)datel_readSpiByte); + } + + const void* GetReadWriteSpiByteFunction() const + { + return GetAddressAtTarget((void*)datel_readWriteSpiByte); + } + + const void* GetWaitSpiByteTimeoutFunction() const + { + return GetAddressAtTarget((void*)datel_waitSpiByteTimeout); + } + + const void* GetReadSpiByteTimeoutFunction() const + { + return GetAddressAtTarget((void*)datel_readSpiByteTimeout); + } +}; + +class DatelSendSDIOCommandPatchCode : public PatchCode +{ +public: + DatelSendSDIOCommandPatchCode(PatchHeap& patchHeap, + const DatelReadSpiBytePatchCode* datelReadSpiBytePatchCode) + : PatchCode(SECTION_START(datel_spi_send), SECTION_SIZE(datel_spi_send), patchHeap) + { + datel_spiSendSDIOCommandR0_ReadWriteSpiByte = (u32)datelReadSpiBytePatchCode->GetReadWriteSpiByteFunction(); + datel_spiSendSDIOCommandR0_ReadSpiByteTimeout = (u32)datelReadSpiBytePatchCode->GetReadSpiByteTimeoutFunction(); + } + + const void* GetSpiSendSDIOCommandR0Function() const + { + return GetAddressAtTarget((void*)datel_spiSendSDIOCommandR0); + } + + const void* GetSpiSendSDIOCommandFunction() const + { + return GetAddressAtTarget((void*)datel_spiSendSDIOCommand); + } +}; diff --git a/arm9/source/patches/platform/datel/DatelSpiCommandsAsm.s b/arm9/source/patches/platform/datel/DatelSpiCommandsAsm.s new file mode 100644 index 0000000..fcdbf95 --- /dev/null +++ b/arm9/source/patches/platform/datel/DatelSpiCommandsAsm.s @@ -0,0 +1,156 @@ +#include "asminc.h" + +.syntax unified +.thumb + +.section "datel_read_spi", "ax" +@ NOTE!!!: This function needs to set r0 last with mov or something similar so that it updates the zero flags +@u8 datel_readSpiByte(void); +BEGIN_ASM_FUNC datel_readSpiByte + movs r0, 0xFF +@u8 datel_readWriteSpiByte(u8); +BEGIN_ASM_FUNC datel_readWriteSpiByte + push {r1-r3, lr} + ldr r3, =REG_MCCNT0 + strh r0, [r3, #2] +1: + ldrh r1, [r3] + lsrs r1, r1, #8 + bcs 1b + @uppper half always 0 + ldrh r0, [r3, #2] + cmp r0, #0 + pop {r1-r3, pc} + +@u8 datel_readSpiByteTimeout(void); +BEGIN_ASM_FUNC datel_readSpiByteTimeout + push {r1-r4, lr} + @ use a timeout of 0x1000 instead of 0xFFF, easier to setup + @ ldr r4, =DATEL_SD_CMD_TIMEOUT_LEN + movs r4, #1 + lsls r4, #12 +1: + bl datel_readSpiByte + cmp r0, #0xFF + bne 1f + subs r4, r4, #1 + bne 1b +1: + pop {r1-r4, pc} + +@bool datel_waitSpiByteTimeout(); +BEGIN_ASM_FUNC datel_waitSpiByteTimeout + push {r1-r4, lr} + @ use a timeout of 0x1000 instead of 0xFFFF, easier to setup + @ ldr r2, =DATEL_SD_WRITE_TIMEOUT_LEN + movs r2, #1 + lsls r2, #16 +1: + bl datel_readSpiByte + bne 1f + subs r2, #1 + bne 1b + + movs r0, #0 + pop {r1-r4, pc} + +1: + movs r0, #1 + pop {r1-r4, pc} + +.section "datel_spi_send", "ax" +@void datel_cycleSpi(); +datel_cycleSpi: + push {r0-r4, lr} + adr r0, datel_cycleSpi_data + ldm r0!, {r1,r3,r4} + strh r3, [r1] @ Enable Spi + movs r0, DATEL_CMD_F2_SPI_ENABLE + + @ datel_SendNtrCommandF2 + movs r2, #0xF2 + lsls r0, r0, #8 + str r2, [r1, #8] + str r0, [r1, #12] + + @ we shift the 0xF2 loaded above by 14 to get 0xXXXX8000 (MCCNT0_ENABLE) + lsls r2, #14 + strh r2, [r1] + @ REG_MCCNT0 + 4 = REG_MCCNT1 + str r4, [r1, #4] +1: + ldr r2, [r1, #4] + cmp r2, #0 + blt 1b + + strh r3, [r1] @ Enable Spi + pop {r0-r4, pc} + +@ NOTE!!!: This function needs to set r0 last with mov or something similar so that it updates the zero flags +@u8 datel_spiSendSDIOCommandR0(u32 arg, u8 cmd); +BEGIN_ASM_FUNC datel_spiSendSDIOCommandR0 + movs r2, 0 +@u8 datel_spiSendSDIOCommand(u32 arg, u8 cmdId, int extraBytes); +BEGIN_ASM_FUNC datel_spiSendSDIOCommand + push {r0-r7, lr} + + bl datel_cycleSpi + + adr r4, datel_spiSendSDIOCommandR0_ReadSpiByteTimeout + @ r3 contains ReadSpiByteTimeout + @ r7 contains ReadWriteSpiByte + ldm r4!, {r3,r7} + + @ we use the cmd and arg directly from the stack + @ r0 is on top, r1 is right below, we read the command id as the last byte pushed of r1, + @ so at sp -1, the command arguments are at sp+0,sp+1,sp+2,sp+3, the 6th byte is garbage data read + @ from sp+4, we don't need it to be something meaningful + mov r4, sp + @ TODO: this could maybe be optimized by setting 4 in r5, and having the last extra byte be sent by the timeout function itself + subs r4, #1 + + movs r5, #6 + + @ sets up lr so that the interwork function jumps back here + bl 1f +1: + subs r5, r5, #1 + ldrb r0, [r4, r5] + @ branch to datel_readWriteSpiByte + bcs datel_spiSendSDIOCommandR0_Interwork + + @ branch to datel_readSpiByteTimeout + bl datel_spiSendSDIOCommandR0_InterworkR3 + + @ load datel_readSpiByte since it's 1 thumb instruction before datel_readWriteSpiByte + subs r7, r7, #2 + + @ Save the return value + movs r6, r0 + + @ sets up lr so that the interwork function jumps back here + bl 1f +1: + subs r2, #1 + @ branch to datel_readSpiByte + bcc datel_spiSendSDIOCommandR0_Interwork + movs r0, r6 + pop {r1} + pop {r1-r7, pc} + +datel_spiSendSDIOCommandR0_Interwork: + bx r7 +datel_spiSendSDIOCommandR0_InterworkR3: + bx r3 +.balign 4 +.pool +datel_cycleSpi_data: + .word REG_MCCNT0 + .word 0x0000A040 @ MCCNT0_MODE_SPI | MCCNT0_SPI_HOLD_CS | MCCNT0_ENABLE + .word 0xA07F6000 @ MCCNT1_RESET_OFF | MCCNT1_CMD_SCRAMBLE | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_LATENCY2(0x3F) +.global datel_spiSendSDIOCommandR0_ReadSpiByteTimeout +datel_spiSendSDIOCommandR0_ReadSpiByteTimeout: + .word 0 +.global datel_spiSendSDIOCommandR0_ReadWriteSpiByte +datel_spiSendSDIOCommandR0_ReadWriteSpiByte: + .word 0 diff --git a/arm9/source/patches/platform/datel/DatelWriteSectorsAsm.h b/arm9/source/patches/platform/datel/DatelWriteSectorsAsm.h new file mode 100644 index 0000000..b884f3e --- /dev/null +++ b/arm9/source/patches/platform/datel/DatelWriteSectorsAsm.h @@ -0,0 +1,34 @@ +#pragma once +#include "sections.h" +#include "../SdWritePatchCode.h" + +DEFINE_SECTION_SYMBOLS(datel_write); + +extern u16 datel_writeSectorSdhcLabel; + +extern u32 datel_SDWriteMultipleSector_SpiSendSDIOCommand; + +extern u32 datel_SDWriteMultipleSector_WaitSpiByteTimeout; +extern u32 datel_SDWriteMultipleSector_ReadSpiByte; + +extern "C" void datel_SDWriteMultipleSector(u32 srcSector, void* dst, u32 sectorCount); + +class DatelWriteSdPatchCode : public SdWritePatchCode +{ +public: + DatelWriteSdPatchCode(PatchHeap& patchHeap, + const DatelReadSpiBytePatchCode* datelReadSpiBytePatchCode, + const DatelSendSDIOCommandPatchCode* datelSendSDIOCommandPatchCode) + : SdWritePatchCode(SECTION_START(datel_write), SECTION_SIZE(datel_write), patchHeap) + { + datel_SDWriteMultipleSector_SpiSendSDIOCommand = (u32)datelSendSDIOCommandPatchCode->GetSpiSendSDIOCommandFunction(); + + datel_SDWriteMultipleSector_WaitSpiByteTimeout = (u32)datelReadSpiBytePatchCode->GetWaitSpiByteTimeoutFunction(); + datel_SDWriteMultipleSector_ReadSpiByte = (u32)datelReadSpiBytePatchCode->GetReadSpiByteFunction(); + } + + const SdWriteFunc GetSdWriteFunction() const override + { + return (const SdWriteFunc)GetAddressAtTarget((void*)datel_SDWriteMultipleSector); + } +}; diff --git a/arm9/source/patches/platform/datel/DatelWriteSectorsAsm.s b/arm9/source/patches/platform/datel/DatelWriteSectorsAsm.s new file mode 100644 index 0000000..9db6db4 --- /dev/null +++ b/arm9/source/patches/platform/datel/DatelWriteSectorsAsm.s @@ -0,0 +1,117 @@ +#include "asminc.h" + +.syntax unified +.thumb + +.section "datel_write", "ax" + +.global datel_writeSectorSdhcLabel + +@ All the called functions leave every registers unchanged, except for r0 in case the function has a return value + +@datel_SDWriteMultipleSector(u32 sector, const u8 * buffer, u32 num_sectors) +BEGIN_ASM_FUNC datel_SDWriteMultipleSector + @ Among those regs, there's r2 being pushed, accessed afterwards from the stack + push {r1-r7, lr} + movs r4, r1 + @ We get the number of bytes total to write, which we'll compare to against in the main loop + lsls r3, r2, #9 + + @ Total written bytes + movs r5, #0 + +datel_writeSectorSdhcLabel: + @ if not sdhc this needs to be shifted to the left by 9 + lsls r0, #9 + @mov r0, r0 + + @ this message needs 1 byte of extra clock before it starts waiting for the start token + movs r1, DATEL_SDIO_CMD25_WRITE_MULTIPLE_BLOCK + movs r2, #1 + ldr r7, datel_SDWriteMultipleSector_SpiSendSDIOCommand + bl datel_SDWriteMultipleSector_Interwork + bne CMD25_not_ok + + @ r6 contains datel_SDWriteMultipleSector_WaitSpiByteTimeout + @ r7 contains datel_SDWriteMultipleSector_ReadSpiByte + adr r1, datel_SDWriteMultipleSector_WaitSpiByteTimeout + ldm r1!, {r6,r7} + @ r1 contains datel_readWriteSpiByte + adds r1, r7, #2 + + @ We use the r2 set above to put 0x10000 to use later as write timeout + @ it's 1 bigger than the timeout len but we save an instruction + @ ldr r2, =DATEL_SD_WRITE_TIMEOUT_LEN + lsls r2, r2, #16 + +write_next_sector: + + @ Send start token + movs r0, DATEL_SPI_MULTI_BLOCK_WRITE_TOKEN + + bl datel_SDWriteMultipleSector_InterworkR1 @ call datel_readWriteSpiByte + +write_next_byte: + ldrb r0, [r4, r5] + bl datel_SDWriteMultipleSector_InterworkR1 @ call datel_readWriteSpiByte + + adds r5, #1 + @ Shifting left by 0x17 will set the Zero flag if the number that was shifted is a multiple + @ of 0x200 (indicating a full sector has been written) + lsls r0, r5, #0x17 + bne write_next_byte + + @ write dummy crc + bl datel_SDWriteMultipleSector_Interwork @ call datel_readSpiByte + bl datel_SDWriteMultipleSector_Interwork @ call datel_readSpiByte + + bl datel_SDWriteMultipleSector_Interwork @ call datel_readSpiByte + + @ we check if the lower nibble is equal to DATEL_SD_WRITE_OK + subs r0, DATEL_SD_WRITE_OK + lsls r0,#28 + bne write_command_failed + + @ Wait for card to write data + bl datel_SDWriteMultipleSector_InterworkR6 @ call datel_waitSpiByteTimeout + beq sector_write_timeout_expired + + @ r3 holds the total number of bytes to write + cmp r3, r5 + bne write_next_sector + + @ send stop token + movs r0, DATEL_SPI_END_MULTI_BLOCK_WRITE + bl datel_SDWriteMultipleSector_InterworkR1 @ call datel_readWriteSpiByte + + @ send 1 byte clock + bl datel_SDWriteMultipleSector_Interwork @ call datel_readSpiByte + + bl datel_SDWriteMultipleSector_InterworkR6 @ call datel_waitSpiByteTimeout + + @ pop {r1-r7, pc} + +CMD25_not_ok: +write_command_failed: +sector_write_timeout_expired: + @ movs r0, #0 + pop {r1-r7, pc} + +datel_SDWriteMultipleSector_Interwork: + bx r7 +datel_SDWriteMultipleSector_InterworkR6: + bx r6 +datel_SDWriteMultipleSector_InterworkR1: + bx r1 +.balign 4 +.pool + +.global datel_SDWriteMultipleSector_SpiSendSDIOCommand +datel_SDWriteMultipleSector_SpiSendSDIOCommand: + .word 0 +.global datel_SDWriteMultipleSector_WaitSpiByteTimeout +datel_SDWriteMultipleSector_WaitSpiByteTimeout: + .word 0 +.global datel_SDWriteMultipleSector_ReadSpiByte +datel_SDWriteMultipleSector_ReadSpiByte: + .word 0 diff --git a/arm9/source/patches/platform/datel/asminc.h b/arm9/source/patches/platform/datel/asminc.h new file mode 100644 index 0000000..5c58efe --- /dev/null +++ b/arm9/source/patches/platform/datel/asminc.h @@ -0,0 +1,25 @@ +.macro BEGIN_ASM_FUNC name + .global \name + .type \name, %function + .align 1 +\name: +.endm + +.equ REG_MCCNT0, 0x040001A0 +.equ REG_MCD0, 0x040001A2 +.equ REG_MCCNT1, 0x040001A4 +.equ REG_MCCMD0, 0x040001A8 + +.equ DATEL_SDIO_CMD18_READ_MULTIPLE_BLOCK, 18 | 0x40 +.equ DATEL_SDIO_CMD12_STOP_TRANSMISSION, 12 | 0x40 +.equ DATEL_SDIO_CMD25_WRITE_MULTIPLE_BLOCK, 25 | 0x40 +.equ DATEL_SPI_MULTI_BLOCK_WRITE_TOKEN, 0xFC +.equ DATEL_SPI_END_MULTI_BLOCK_WRITE, 0xFD +.equ DATEL_SPI_START_DATA_TOKEN, 0xFE +.equ DATEL_SD_CMD_TIMEOUT_LEN, 0xFFF + +.equ DATEL_SD_WRITE_OK, 0x5 +.equ DATEL_SD_WRITE_TIMEOUT_LEN, 0xFFFF + +.equ DATEL_CMD_F2_SPI_ENABLE, 0xCC +.equ DATEL_CMD_F2_SPI_DISABLE, 0xC8 \ No newline at end of file