DSTT: Add DMA support and reduce code size (#162) Fixes #111

This commit is contained in:
lifehackerhansol
2026-05-10 00:37:38 -07:00
committed by GitHub
parent ae034ae1f6
commit e227cff9ee
12 changed files with 452 additions and 140 deletions

View File

@@ -3,10 +3,12 @@
## [Unreleased] ## [Unreleased]
### Added ### Added
- DMA card read support for the DSTT platform - by @lifehackerhansol
- DMA card read support for the M3DS platform - by @lifehackerhansol - DMA card read support for the M3DS platform - by @lifehackerhansol
### Fixed ### Fixed
- DMA card read offsets for Pokemon Ranger (EU) - by @taxicat1 - DMA card read offsets for Pokemon Ranger (EU) - by @taxicat1
- Split DSTT SD read to multiple patches to avoid running out of space - by @lifehackerhansol
## [v1.6.0] - 29 Mar 2026 ## [v1.6.0] - 29 Mar 2026

View File

@@ -30,7 +30,7 @@ Note that there can be some game compatibility differences between different pla
| AKRPG | Acekard RPG SD card | ❌ | | AKRPG | Acekard RPG SD card | ❌ |
| DATEL | DATEL devices consisting of GAMES n' MUSIC and Action Replay DS(i) Media Edition | ❌ | | DATEL | DATEL devices consisting of GAMES n' MUSIC and Action Replay DS(i) Media Edition | ❌ |
| DSPICO | DSpico | ✅ | | DSPICO | DSpico | ✅ |
| DSTT | DSTT, SuperCard DSONE SDHC, r4isdhc.com carts 2014+, r4i-sdhc.com carts, various derivatives | | | DSTT | DSTT, SuperCard DSONE SDHC, r4isdhc.com carts 2014+, r4i-sdhc.com carts, various derivatives | |
| EZP | EZ-Flash Parallel | ❌ | | EZP | EZ-Flash Parallel | ❌ |
| G003 | M3i Zero (GMP-Z003) | ✅ | | G003 | M3i Zero (GMP-Z003) | ✅ |
| ISNITRO | Supports the IS-NITRO-EMULATOR through agb semihosting. | ❌ | | ISNITRO | Supports the IS-NITRO-EMULATOR through agb semihosting. | ❌ |

View File

@@ -190,7 +190,7 @@ bool DsttLoaderPlatform::InitializeSdCard(void)
if (isSdhc) if (isSdhc)
{ {
sdHostSetRegister(DSTT_SD_HOST_REG_RESET | DSTT_SD_HOST_REG_CLEAN_ROM_MODE | DSTT_SD_HOST_REG_SDHC); sdHostSetRegister(DSTT_SD_HOST_REG_RESET | DSTT_SD_HOST_REG_CLEAN_ROM_MODE | DSTT_SD_HOST_REG_SDHC);
dstt_readSd_sdsc_shift = THUMB_MOVS_REG(THUMB_R7, THUMB_R0); dstt_readSd_sdsc_shift = THUMB_MOVS_REG(THUMB_R6, THUMB_R0);
dstt_writeSd_sdsc_shift = THUMB_NOP; dstt_writeSd_sdsc_shift = THUMB_NOP;
} }
return true; return true;

View File

@@ -1,7 +1,10 @@
#pragma once #pragma once
#include "../LoaderPlatform.h" #include "../LoaderPlatform.h"
#include "DsttReadSdPatchCode.h" #include "DsttReadSdPatchCode.h"
#include "DsttReadSdDmaPatchCode.h"
#include "DsttWriteSdPatchCode.h" #include "DsttWriteSdPatchCode.h"
#include "DsttSdStopTransmissionPatchCode.h"
#include "DsttReadSdHelperPatchCode.h"
/// @brief Implementation of LoaderPlatform for the DSTT flashcard /// @brief Implementation of LoaderPlatform for the DSTT flashcard
class DsttLoaderPlatform : public LoaderPlatform class DsttLoaderPlatform : public LoaderPlatform
@@ -15,11 +18,30 @@ public:
return new DsttReadSdPatchCode(patchHeap, return new DsttReadSdPatchCode(patchHeap,
patchCodeCollection.GetOrAddSharedPatchCode([&] patchCodeCollection.GetOrAddSharedPatchCode([&]
{ {
return new DsttReadSdStopTransmissionPatchCode(patchHeap); return new DsttSdStopTransmissionPatchCode(patchHeap);
}),
patchCodeCollection.GetOrAddSharedPatchCode([&]
{
return new DsttReadSdHelperPatchCode(patchHeap);
})); }));
}); });
} }
const IReadSectorsDmaPatchCode* CreateSdReadDmaPatchCode(PatchCodeCollection& patchCodeCollection,
PatchHeap& patchHeap, const void* miiCardDmaCopy32Ptr) const override
{
return patchCodeCollection.AddUniquePatchCode<DsttReadSdDmaPatchCode>(
patchHeap, miiCardDmaCopy32Ptr,
patchCodeCollection.GetOrAddSharedPatchCode([&]
{
return new DsttSdStopTransmissionPatchCode(patchHeap);
}),
patchCodeCollection.GetOrAddSharedPatchCode([&]
{
return new DsttReadSdHelperPatchCode(patchHeap);
}));
}
const IWriteSectorsPatchCode* CreateSdWritePatchCode( const IWriteSectorsPatchCode* CreateSdWritePatchCode(
PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override
{ {
@@ -35,5 +57,7 @@ public:
LoaderPlatformType GetPlatformType() const override { return LoaderPlatformType::Slot1; } LoaderPlatformType GetPlatformType() const override { return LoaderPlatformType::Slot1; }
bool HasDmaSdReads() const override { return true; }
bool InitializeSdCard() override; bool InitializeSdCard() override;
}; };

View File

@@ -0,0 +1,44 @@
#pragma once
#include "sections.h"
#include "patches/PatchCode.h"
#include "../IReadSectorsDmaPatchCode.h"
#include "DsttSdStopTransmissionPatchCode.h"
#include "DsttReadSdHelperPatchCode.h"
DEFINE_SECTION_SYMBOLS(dstt_readsddma);
extern "C" void dstt_readSdDma(u32 srcSector, u32 previousSrcSector, u32 dmaChannel, void* dst);
extern u32 dstt_readSdDma_applySectorCommand_address;
extern u32 dstt_readSdDma_waitDataReady_address;
extern u32 dstt_readSdDma_stopTransmission_address;
extern u32 dstt_readSdDma_miiCardDmaCopy32Ptr;
class DsttReadSdDmaPatchCode : public PatchCode, public IReadSectorsDmaPatchCode
{
public:
DsttReadSdDmaPatchCode(PatchHeap& patchHeap, const void* miiCardDmaCopy32Ptr,
const DsttSdStopTransmissionPatchCode* dsttSdStopTransmissionPatchCode,
const DsttReadSdHelperPatchCode* dsttReadSdHelperPatchCode)
: PatchCode(SECTION_START(dstt_readsddma), SECTION_SIZE(dstt_readsddma), patchHeap)
{
dstt_readSdDma_miiCardDmaCopy32Ptr = (u32)miiCardDmaCopy32Ptr;
dstt_readSdDma_stopTransmission_address = (u32)dsttSdStopTransmissionPatchCode->GetStopTransmissionFunction();
dstt_readSdDma_applySectorCommand_address = (u32)dsttReadSdHelperPatchCode->GetApplySectorCommandFunction();
dstt_readSdDma_waitDataReady_address = (u32)dsttReadSdHelperPatchCode->GetWaitDataReadyFunction();
_sdReadDmaFinishFunc = (ReadSectorsDmaFinishFunc)dsttSdStopTransmissionPatchCode->GetStopTransmissionFunction();
}
const ReadSectorsDmaFunc GetReadSectorsDmaFunction() const override
{
return (const ReadSectorsDmaFunc)GetAddressAtTarget((void*)dstt_readSdDma);
}
const ReadSectorsDmaFinishFunc GetReadSectorsDmaFinishFunction() const override
{
return _sdReadDmaFinishFunc;
}
private:
ReadSectorsDmaFinishFunc _sdReadDmaFinishFunc = 0;
};

View File

@@ -0,0 +1,111 @@
.cpu arm946e-s
.section "dstt_readsddma", "ax"
.syntax unified
.thumb
// r0 = src sector
// r1 = previous src sector
// r2 = dma channel
// r3 = dst
.global dstt_readSdDma
.type dstt_readSdDma, %function
dstt_readSdDma:
push {r2-r7,lr}
ldr r4, =0x040001A0
movs r3, #0x80
strb r3, [r4,#1]
adr r2, dstt_romSettings
// r2 - dstt_romSettings
// r3 - dstt_readSdDma_applySectorCommand_address
// r5 - dstt_readSdDma_stopTransmission_address
// r6 - dstt_readSdDma_waitDataReady_address
// r7 - dstt_readSdDma_miiCardDmaCopy32Ptr
ldmia r2, {r2, r3, r5, r6, r7}
cmp r1, #0
beq applySector
subs r1, r0, r1
cmp r1, #1 // if sequential
beq sector_loop_sequential
blx r5 // dstt_readSdDma_stopTransmission_address
applySector:
blx r3 // dstt_readSdDma_applySectorCommand_address
b sector_loop
sector_loop_sequential:
// set sd host mode DSTT_MODE_RECEIVE_DATA_BLOCK_STOP_CLOCK
// 51 00 00 00 00 00 07 00
movs r1, #0x51
str r1, [r4, #0x8]
movs r1, #7
lsls r1, r1, #16
str r1, [r4, #0xC]
sector_loop:
str r2, [r4,#4]
movs r1, #0x41
lsls r1, r1, #20 // r1 = 0x04100000
cmd_wait_loop:
ldrb r3, [r4,#6]
lsrs r3, r3, #8
bcc cmd_wait_loop
ldr r3, [r1, #0x10]
blx r6 // dstt_readSdDma_waitDataReady_address
// previous push had r2 = dma channel, r3 = dst at top of stack
// pop to the correct register for miiCardDmaCopy32
pop {r0, r2}
adds r1, #0x10 // Source. r1 is 0x04100000 from the calling function
movs r3, #1
lsls r3, r3, #9 // (1 << 9) = 512 = count
blx r7 // dstt_readSdDma_miiCardDmaCopy32Ptr
cmd81_poll_dma:
// read sd fifo
// 81 xx xx xx xx xx xx xx
// xx bytes are leftover from previous command
movs r1, #0x81
strb r1, [r4,#0x8]
movs r1, #0xC0 // select rom mode, with irq
strb r1, [r4,#0x1]
movs r1, #0xA1
strb r1, [r4,#7]
pop {r4-r7,pc}
.balign 4
dstt_romSettings:
.word 0xA7180000
.global dstt_readSdDma_applySectorCommand_address
dstt_readSdDma_applySectorCommand_address:
.word 0
.global dstt_readSdDma_stopTransmission_address
dstt_readSdDma_stopTransmission_address:
.word 0
.global dstt_readSdDma_waitDataReady_address
dstt_readSdDma_waitDataReady_address:
.word 0
.global dstt_readSdDma_miiCardDmaCopy32Ptr
dstt_readSdDma_miiCardDmaCopy32Ptr:
.word 0
.pool
.end

View File

@@ -0,0 +1,34 @@
#pragma once
#include "sections.h"
#include "thumbInstructions.h"
#include "patches/PatchCode.h"
DEFINE_SECTION_SYMBOLS(dstt_readsd_helper);
extern "C" void dstt_readSd_applySectorCommand(void);
extern "C" void dstt_readSd_waitDataReady(void);
extern "C" void dstt_readSd_transferData();
extern u16 dstt_readSd_sdsc_shift;
class DsttReadSdHelperPatchCode : public PatchCode
{
public:
explicit DsttReadSdHelperPatchCode(PatchHeap& patchHeap)
: PatchCode(SECTION_START(dstt_readsd_helper), SECTION_SIZE(dstt_readsd_helper), patchHeap) { }
const void* GetApplySectorCommandFunction() const
{
return GetAddressAtTarget((void*)dstt_readSd_applySectorCommand);
}
const void* GetWaitDataReadyFunction() const
{
return GetAddressAtTarget((void*)dstt_readSd_waitDataReady);
}
const void* GetTransferDataFunction() const
{
return GetAddressAtTarget((void*)dstt_readSd_transferData);
}
};

View File

@@ -0,0 +1,103 @@
.cpu arm7tdmi
.section "dstt_readsd_helper", "ax"
.syntax unified
.thumb
// r0 = src sector
.global dstt_readSd_applySectorCommand
.type dstt_readSd_applySectorCommand, %function
dstt_readSd_applySectorCommand:
push {r5-r6,lr}
.global dstt_readSd_sdsc_shift
dstt_readSd_sdsc_shift:
lsls r6, r0, #9
// request sd read for sector 0xaabbccdd
// 54 aa bb cc dd 00 00 00
movs r5, #0x54
strb r5, [r4,#0x8]
lsrs r5, r6, #24
strb r5, [r4,#0x9]
lsrs r5, r6, #16
strb r5, [r4,#0xA]
lsrs r5, r6, #8
strb r5, [r4,#0xB]
// make sure the last 3 bytes are zero
movs r5, #0xFF
ands r6, r5
str r6, [r4,#0xC] // storing as little-endian puts the bottom 8 bits as first byte
pop {r5-r6,pc}
.balign 4
.global dstt_readSd_waitDataReady
.type dstt_readSd_waitDataReady, %function
dstt_readSd_waitDataReady:
push {r5-r7,lr}
movs r6, #0x41
lsls r6, r6, #20 // r6 = 0x04100000
// sd fifo full poll
// 80 xx xx xx xx xx xx xx
// xx bytes are leftover from previous command
movs r7, #0x80
strb r7, [r4,#0x8]
ldr r7, =0xA7180000
cmd80_poll_loop:
str r7, [r4,#4]
cmd80_wait_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8
bcc cmd80_wait_loop
ldr r5, [r6, #0x10]
cmp r5, #0
bne cmd80_poll_loop
pop {r5-r7,pc}
.balign 4
// r1 = dst, will be incremented by 512
.global dstt_readSd_transferData
.type dstt_readSd_transferData, %function
dstt_readSd_transferData:
push {r5-r7,lr}
ldr r5, =0xA7180000
movs r6, #0x41
lsls r6, r6, #20 // r6 = 0x04100000
// read sd fifo
// 81 xx xx xx xx xx xx xx
// xx bytes are leftover from previous command
movs r5, #0x81
strb r5, [r4,#0x8]
movs r5, #0xA1
strb r5, [r4,#7]
cmd81_data_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8 // check if data is ready
bcc cmd81_data_loop_check_transfer_end // if not skip reading
ldr r5, [r6, #0x10]
stmia r1!, {r5}
cmd81_data_loop_check_transfer_end:
ldrb r5, [r4,#7]
lsrs r5, r5, #8 // check if transfer is done
bcs cmd81_data_loop
pop {r5-r7,pc}
.balign 4
.pool
.end

View File

@@ -3,36 +3,30 @@
#include "thumbInstructions.h" #include "thumbInstructions.h"
#include "patches/PatchCode.h" #include "patches/PatchCode.h"
#include "../IReadSectorsPatchCode.h" #include "../IReadSectorsPatchCode.h"
#include "DsttSdStopTransmissionPatchCode.h"
#include "DsttReadSdHelperPatchCode.h"
DEFINE_SECTION_SYMBOLS(dstt_readsd); DEFINE_SECTION_SYMBOLS(dstt_readsd);
DEFINE_SECTION_SYMBOLS(dstt_readsd_stopTransmission);
extern "C" void dstt_readSd(u32 srcSector, void* dst, u32 sectorCount); extern "C" void dstt_readSd(u32 srcSector, void* dst, u32 sectorCount);
extern "C" void dstt_stopTransmission();
extern u32 dstt_readSd_applySectorCommand_address;
extern u32 dstt_waitDataReady_address;
extern u32 dstt_transferData_address;
extern u32 dstt_stopTransmission_address; extern u32 dstt_stopTransmission_address;
extern u16 dstt_readSd_sdsc_shift;
class DsttReadSdStopTransmissionPatchCode : public PatchCode
{
public:
explicit DsttReadSdStopTransmissionPatchCode(PatchHeap& patchHeap)
: PatchCode(SECTION_START(dstt_readsd_stopTransmission), SECTION_SIZE(dstt_readsd_stopTransmission), patchHeap) { }
const void* GetStopTransmissionFunction() const
{
return GetAddressAtTarget((void*)dstt_stopTransmission);
}
};
class DsttReadSdPatchCode : public PatchCode, public IReadSectorsPatchCode class DsttReadSdPatchCode : public PatchCode, public IReadSectorsPatchCode
{ {
public: public:
explicit DsttReadSdPatchCode(PatchHeap& patchHeap, explicit DsttReadSdPatchCode(PatchHeap& patchHeap,
const DsttReadSdStopTransmissionPatchCode* dsttReadSdStopTransmissionPatchCode) const DsttSdStopTransmissionPatchCode* dsttSdStopTransmissionPatchCode,
const DsttReadSdHelperPatchCode* dsttReadSdHelperPatchCode)
: PatchCode(SECTION_START(dstt_readsd), SECTION_SIZE(dstt_readsd), patchHeap) : PatchCode(SECTION_START(dstt_readsd), SECTION_SIZE(dstt_readsd), patchHeap)
{ {
dstt_stopTransmission_address = (u32)dsttReadSdStopTransmissionPatchCode->GetStopTransmissionFunction(); dstt_readSd_applySectorCommand_address = (u32)dsttReadSdHelperPatchCode->GetApplySectorCommandFunction();
dstt_stopTransmission_address = (u32)dsttSdStopTransmissionPatchCode->GetStopTransmissionFunction();
dstt_waitDataReady_address = (u32)dsttReadSdHelperPatchCode->GetWaitDataReadyFunction();
dstt_transferData_address = (u32)dsttReadSdHelperPatchCode->GetTransferDataFunction();
} }
const ReadSectorsFunc GetReadSectorsFunction() const override const ReadSectorsFunc GetReadSectorsFunction() const override

View File

@@ -11,27 +11,11 @@
dstt_readSd: dstt_readSd:
push {r4-r7,lr} push {r4-r7,lr}
.global dstt_readSd_sdsc_shift ldr r3, =0xA7180000
dstt_readSd_sdsc_shift: ldr r4, =0x040001A0
lsls r7, r0, #9
adr r0, dstt_stopTransmission_address ldr r7, dstt_readSd_applySectorCommand_address
ldmia r0, {r0, r3, r4} bl blx_r7
// request sd read for sector 0xaabbccdd
// 54 aa bb cc dd 00 00 00
movs r5, #0x54
strb r5, [r4,#0x8]
lsrs r5, r7, #24
strb r5, [r4,#0x9]
lsrs r5, r7, #16
strb r5, [r4,#0xA]
lsrs r5, r7, #8
strb r5, [r4,#0xB]
// make sure the last 3 bytes are zero
movs r5, #0xFF
ands r7, r5
str r7, [r4,#0xC] // storing as little-endian puts the bottom 8 bits as first byte
movs r7, #0x80 movs r7, #0x80
strb r7, [r4,#1] strb r7, [r4,#1]
@@ -48,42 +32,11 @@ cmd54_wait_loop:
bcc cmd54_wait_loop bcc cmd54_wait_loop
ldr r5, [r6, #0x10] ldr r5, [r6, #0x10]
// sd fifo full poll ldr r7, dstt_waitDataReady_address
// 80 xx xx xx xx xx xx xx bl blx_r7
// xx bytes are leftover from previous command
strb r7, [r4,#0x8]
cmd80_poll_loop: ldr r7, dstt_transferData_address
str r3, [r4,#4] bl blx_r7
cmd80_wait_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8
bcc cmd80_wait_loop
ldr r5, [r6, #0x10]
cmp r5, #0
bne cmd80_poll_loop
// read sd fifo
// 81 xx xx xx xx xx xx xx
// xx bytes are leftover from previous command
movs r5, #0x81
strb r5, [r4,#0x8]
movs r5, #0xA1
strb r5, [r4,#7]
cmd81_data_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8 // check if data is ready
bcc cmd81_data_loop_check_transfer_end // if not skip reading
ldr r5, [r6, #0x10]
stmia r1!, {r5}
cmd81_data_loop_check_transfer_end:
ldrb r5, [r4,#7]
lsrs r5, r5, #8 // check if transfer is done
bcs cmd81_data_loop
subs r2, #1 subs r2, #1
beq stop_transmission beq stop_transmission
@@ -99,77 +52,31 @@ cmd81_data_loop_check_transfer_end:
b sector_loop b sector_loop
stop_transmission: stop_transmission:
bx r0 ldr r7, dstt_stopTransmission_address
bl blx_r7
pop {r4-r7,pc}
blx_r7:
bx r7
.balign 4 .balign 4
.global dstt_readSd_applySectorCommand_address
dstt_readSd_applySectorCommand_address:
.word 0
.global dstt_waitDataReady_address
dstt_waitDataReady_address:
.word 0
.global dstt_transferData_address
dstt_transferData_address:
.word 0
.global dstt_stopTransmission_address .global dstt_stopTransmission_address
dstt_stopTransmission_address: dstt_stopTransmission_address:
.word 0 .word 0
settings:
.word 0xA7180000
regbase:
.word 0x040001A0
.pool
.section "dstt_readsd_stopTransmission", "ax"
.thumb
.global dstt_stopTransmission
.type dstt_stopTransmission, %function
dstt_stopTransmission:
// send SD_CMD12_STOP_TRANSMISSION
// set sd host mode DSTT_MODE_SDCMD_RESPONSE_32BIT, SD_CMD12_STOP_TRANSMISSION
// 51 00 00 00 00 0C 01 00
movs r5, #0x51
str r5, [r4,#0x8]
ldr r5,= 0x00010C00
str r5, [r4,#0xC]
str r3, [r4,#4]
cmd51_1_wait_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8
bcc cmd51_1_wait_loop
ldr r5, [r6, #0x10]
// sd busy poll
// 50 xx xx xx xx xx xx xx
// xx bytes are leftover from previous command
movs r5, #0x50
strb r5, [r4,#0x8]
cmd50_poll_loop:
str r3, [r4,#4]
cmd50_wait_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8
bcc cmd50_wait_loop
ldr r5, [r6, #0x10]
cmp r5, #0
bne cmd50_poll_loop
// read sd response
// 52 xx xx xx xx xx xx xx
// xx bytes are leftover from previous command
movs r5, #0x52
strb r5, [r4,#0x8]
str r3, [r4,#4]
cmd52_wait_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8
bcc cmd52_wait_loop
ldr r5, [r6, #0x10]
pop {r4-r7,pc}
.balign 4
.pool .pool
.end .end

View File

@@ -0,0 +1,20 @@
#pragma once
#include "sections.h"
#include "thumbInstructions.h"
#include "patches/PatchCode.h"
DEFINE_SECTION_SYMBOLS(dstt_sdstoptransmission);
extern "C" void dstt_sdStopTransmission();
class DsttSdStopTransmissionPatchCode : public PatchCode
{
public:
explicit DsttSdStopTransmissionPatchCode(PatchHeap& patchHeap)
: PatchCode(SECTION_START(dstt_sdstoptransmission), SECTION_SIZE(dstt_sdstoptransmission), patchHeap) { }
const void* GetStopTransmissionFunction() const
{
return GetAddressAtTarget((void*)dstt_sdStopTransmission);
}
};

View File

@@ -0,0 +1,73 @@
.cpu arm7tdmi
.syntax unified
.thumb
.section "dstt_sdstoptransmission", "ax"
.global dstt_sdStopTransmission
.type dstt_sdStopTransmission, %function
dstt_sdStopTransmission:
// send SD_CMD12_STOP_TRANSMISSION
push {r3-r6,lr}
ldr r3, =0xA7180000
ldr r4, =0x040001A0
// select rom mode, without irq
// the caller may not have ensured this is set
movs r6, #0x80
strb r6, [r4,#1]
// set sd host mode DSTT_MODE_SDCMD_RESPONSE_32BIT, SD_CMD12_STOP_TRANSMISSION
// 51 00 00 00 00 0C 01 00
movs r5, #0x51
str r5, [r4,#0x8]
ldr r5,= 0x00010C00
str r5, [r4,#0xC]
str r3, [r4,#0x4]
movs r6, #0x41
lsls r6, r6, #20 // r6 = 0x04100000
cmd51_1_wait_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8
bcc cmd51_1_wait_loop
ldr r5, [r6, #0x10]
// sd busy poll
// 50 xx xx xx xx xx xx xx
// xx bytes are leftover from previous command
movs r5, #0x50
strb r5, [r4,#0x8]
cmd50_poll_loop:
str r3, [r4,#4]
cmd50_wait_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8
bcc cmd50_wait_loop
ldr r5, [r6, #0x10]
cmp r5, #0
bne cmd50_poll_loop
// read sd response
// 52 xx xx xx xx xx xx xx
// xx bytes are leftover from previous command
movs r5, #0x52
strb r5, [r4,#0x8]
str r3, [r4,#4]
cmd52_wait_loop:
ldrb r5, [r4,#6]
lsrs r5, r5, #8
bcc cmd52_wait_loop
ldr r5, [r6, #0x10]
pop {r3-r6,pc}
.balign 4
.pool
.end