Add support for DATEL devices (GAMES n' MUSIC and Action Replay DS(i) Media Edition) (#64)

This commit is contained in:
Edoardo Lolletti
2025-12-28 20:25:22 +01:00
committed by GitHub
parent 7134c4b330
commit 26f27a4138
14 changed files with 744 additions and 0 deletions

View File

@@ -19,6 +19,7 @@ jobs:
"ACE3DS",
"AK2",
"AKRPG",
"DATEL",
"DSPICO",
"DSTT",
"EZP",

View File

@@ -12,6 +12,7 @@ jobs:
"ACE3DS",
"AK2",
"AKRPG",
"DATEL",
"DSPICO",
"DSTT",
"EZP",

View File

@@ -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 | ❌ |

View File

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

View File

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

View File

@@ -0,0 +1,154 @@
#include "common.h"
#include <libtwl/card/card.h>
#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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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