Files
pico-loader/arm9/source/patches/platform/datel/DatelSpiCommandsAsm.s
Edoardo Lolletti 992e2d1053 DATEL: Make read sector function vram safe (#80)
* DATEL: Make read sector function vram safe

The platform code was performing reads in single byte units, breaking in case the destination buffer was in vram.
This caused issues with some games (e.g. Castelvania Order of Ecclesia), and also broke soft resetting.

* Halve wait timeout on sector read since we read 2 bytes at the time
2025-12-30 20:10:02 +01:00

178 lines
4.8 KiB
ArmAsm

#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
@ Wait if there's a transfer in progress (can happen if the random byte sent by the cycle spi function is still on its way)
1:
ldrh r1, [r3]
lsrs r1, r1, #8
bcs 1b
@ Actually write the byte of interest and wait for it to be sent
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}
@u16 datel_readWriteSpiShort();
BEGIN_ASM_FUNC datel_readSpiShort
push {r1-r7, lr}
bl datel_readSpiByte
movs r1, r0
bl datel_readSpiByte
lsls r0, #8
orrs r0, r1
pop {r1-r7, 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-r5, lr}
adr r0, datel_cycleSpi_data
ldm r0!, {r1,r3,r4}
@ First send spi disable command, the second time this loop is called we send spi enable
movs r5, DATEL_CMD_F2_SPI_DISABLE
datel_sendNtrCommandF2:
movs r2, #0xF2
lsls r0, r5, #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
adds r5, #4
cmp r5, DATEL_CMD_F2_SPI_ENABLE
@ Enable spi and also send a dummy byte at the same time by writing first to REG_MCCNT0 and then to REG_MCD0
str r3, [r1]
beq datel_sendNtrCommandF2
pop {r0-r5, 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 0x00FFA040 @ MCCNT0_MODE_SPI | MCCNT0_SPI_HOLD_CS | MCCNT0_ENABLE in lower 16 bit, 0xFF in upper 16
.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