2022-08-04 11:40:24 +08:00

288 lines
8.9 KiB
C

/*
* This file is part of open_agb_firm
* Copyright (C) 2021 derrek, profi200
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that 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 "types.h"
#include "drivers/toshsd.h"
#include "drivers/toshsd_config.h"
#ifdef _3DS
#ifdef ARM9
#include "arm9/drivers/interrupt.h"
#include "arm9/drivers/cfg9.h"
#elif ARM11
#include "arm11/drivers/interrupt.h"
#endif // #ifdef ARM9
#elif TWL
// TODO: DSi IRQ stuff.
#endif // #ifdef _3DS
static void toshsdIsr(UNUSED u32 id)
{
Toshsd *const regs = getToshsdRegs(TOSHSD_SLOT_PORT / 2u);
regs->sd_status = ~(STATUS_INSERT | STATUS_REMOVE);
// TODO: Some kind of event to notify the main loop.
}
void TOSHSD_init(void)
{
#if (_3DS && ARM9)
// Note: The power bits don't affect regular card detect. Port remapping does.
// TODO: Can we switch controllers/ports glitch-free?
const u32 slotPort = (TOSHSD_SLOT_PORT == 2u ? SDMMCCTL_SLOT_TOSHSD3_SEL : SDMMCCTL_SLOT_TOSHSD1_SEL);
const u32 c2Map = (TOSHSD_C2_MAP == 1u ? SDMMCCTL_TOSHSD3_MAP11 : SDMMCCTL_TOSHSD3_MAP9);
getCfg9Regs()->sdmmcctl = slotPort | c2Map | SDMMCCTL_UNK_BIT6;
#endif // #if (_3DS && ARM9)
// TODO: 3DS: Do we get controller 3 IRQs on the side the controller is NOT mapped to?
#ifdef _3DS
#ifdef ARM9
IRQ_registerIsr(IRQ_SDIO_1, NULL);
IRQ_registerIsr(IRQ_SDIO_3, toshsdIsr);
// IRQ_SDIO_1_ASYNC not needed.
// IRQ_SDIO_3_ASYNC not needed.
#elif ARM11
IRQ_registerIsr(IRQ_TOSHSD2, 14, 0, NULL);
IRQ_registerIsr(IRQ_TOSHSD3, 14, 0, toshsdIsr);
//IRQ_registerIsr(IRQ_TOSHSD2_IRQ, 14, 0, toshsdIsr); // TODO: Should we register this externally?
// IRQ_SDIO3_IRQ not needed.
#endif // #ifdef ARM9
#elif TWL
// TODO: DSi IRQ stuff.
#endif // #ifdef _3DS
// Reset all controllers.
for(u8 i = 0; i < 2; i++) // TODO: 3DS: Don't touch controller 3 if not mapped.
{
Toshsd *const regs = getToshsdRegs(i);
// Setup 32 bit FIFO.
regs->sd_fifo32_cnt = FIFO32_CLEAR | FIFO32_EN;
regs->sd_blocklen32 = 512;
regs->sd_blockcount32 = 1;
regs->dma_ext_mode = DMA_EXT_DMA_MODE;
// Reset. Unlike similar controllers no delay is needed.
regs->soft_rst = SOFT_RST_RST;
regs->soft_rst = SOFT_RST_NORST;
regs->sd_portsel = PORTSEL_P0;
regs->sd_blockcount = 1;
regs->sd_status_mask = STATUS_MASK_DEFAULT;
regs->sd_clk_ctrl = SD_CLK_DIV_128;
regs->sd_blocklen = 512;
regs->sd_option = OPTION_BUS_WIDTH1 | OPTION_UNK14 | 0xE9; // ~7 ms card detection time.
regs->ext_cdet_mask = EXT_CDET_MASK_ALL;
regs->ext_cdet_dat3_mask = EXT_CDET_DAT3_MASK_ALL;
// SDIO init here?
}
}
void TOSHSD_deinit(void)
{
for(u8 i = 0; i < 2; i++) // TODO: 3DS: Don't touch controller 3 if not mapped.
{
Toshsd *const regs = getToshsdRegs(i);
// TODO: Handle this differently. The last block of the previous
// write transfer may still be in progress.
// TODO: Why is waiting for CMD_BUSY getting cleared not enough?
// Hangs on 2 single block writes in a row otherwise.
while((regs->sd_status & (1u<<29 | STATUS_CMD_BUSY)) != 1u<<29);
}
#if (_3DS && ARM9)
getCfg9Regs()->sdmmcctl = SDMMCCTL_UNK_BIT6 | SDMMCCTL_SLOT_PWR_OFF;
#endif // #if (_3DS && ARM9)
}
void TOSHSD_initPort(ToshsdPort *const port, u8 portNum)
{
// Reset port state.
port->portNum = portNum;
port->sd_clk_ctrl = SD_CLK_DIV_128;
port->sd_blocklen = 512;
port->sd_option = OPTION_BUS_WIDTH1 | OPTION_UNK14 | 0xE9;
}
static void setPort(Toshsd *const regs, const ToshsdPort *const port)
{
// TODO: Can we somehow prevent all these reg writes each time?
// Maybe some kind of dirty flag + active port check?
regs->sd_portsel = port->portNum % 2u;
regs->sd_clk_ctrl = port->sd_clk_ctrl;
const u16 blocklen = port->sd_blocklen;
regs->sd_blocklen = blocklen;
regs->sd_option = port->sd_option;
regs->sd_blocklen32 = blocklen;
}
bool TOSHSD_cardDetected(void)
{
return getToshsdRegs(TOSHSD_SLOT_PORT / 2u)->sd_status & STATUS_DETECT;
}
bool TOSHSD_cardSliderUnlocked(void)
{
return getToshsdRegs(TOSHSD_SLOT_PORT / 2u)->sd_status & STATUS_NO_WRPROT;
}
// TODO: Clock in Hz?
void TOSHSD_setClock(ToshsdPort *const port, u16 clk)
{
// On SD/MMC init we have to permanently turn on clock
// for a while so this needs to immediately take effect.
port->sd_clk_ctrl = clk;
getToshsdRegs(port->portNum / 2u)->sd_clk_ctrl = clk;
}
static void getResponse(Toshsd *const regs, ToshsdPort *const port, u16 cmd)
{
if((cmd & CMD_RESP_MASK) != CMD_RESP_R2)
{
port->resp[0] = regs->sd_resp[0];
}
else // 136 bit responses need special treatment...
{
u32 resp[4];
for(u32 i = 0; i < 4; i++) resp[i] = regs->sd_resp[i];
port->resp[0] = resp[3]<<8 | resp[2]>>24;
port->resp[1] = resp[2]<<8 | resp[1]>>24;
port->resp[2] = resp[1]<<8 | resp[0]>>24;
port->resp[3] = resp[0]<<8; // TODO: Add the missing CRC7 and the always 1 bit?
}
}
static void doCpuTransfer(Toshsd *const regs, u16 cmd, u32 *buf)
{
const u32 blockLen = regs->sd_blocklen; // | TODO: GCC adds a weird & to one of these 2.
u32 blockCount = regs->sd_blockcount; // |
vu32 *const fifo = getToshsdFifo(regs);
if(cmd & CMD_DIR_R)
{
do
{
__wfi();
if(regs->sd_fifo32_cnt & FIFO32_FULL) // RX ready.
{
const u32 *const blockEnd = buf + (blockLen / 4);
do
{
buf[0] = *fifo;
buf[1] = *fifo;
buf[2] = *fifo;
buf[3] = *fifo;
buf += 4;
} while(buf < blockEnd);
blockCount--;
}
// TODO: Check detect bit (needs insert/remove IRQ handling).
// TODO: Use DATA_END IRQ instead of counter?
} while((regs->sd_status & STATUS_MASK_ERR) == 0 && blockCount);
}
else
{
// TODO: Write first block ahead of time.
// gbatek Command/Param/Response/Data at bottom of page.
do
{
__wfi();
if(!(regs->sd_fifo32_cnt & FIFO32_NOT_EMPTY)) // TX request.
{
const u32 *const blockEnd = buf + (blockLen / 4);
do
{
*fifo = buf[0];
*fifo = buf[1];
*fifo = buf[2];
*fifo = buf[3];
buf += 4;
} while(buf < blockEnd);
blockCount--;
}
// TODO: Check detect bit (needs insert/remove IRQ handling).
// TODO: Use DATA_END IRQ instead of counter? This may be difficult for write
// if we want the last block to be processed in background.
} while((regs->sd_status & STATUS_MASK_ERR) == 0 && blockCount);
}
}
u32 TOSHSD_sendCommand(ToshsdPort *const port, u16 cmd, u32 arg)
{
Toshsd *const regs = getToshsdRegs(port->portNum / 2u); // TODO: gcc generates a udf instruction for this line.
// TODO: Handle this differently. The last block of the previous
// write transfer may still be in progress.
// TODO: Why is waiting for CMD_BUSY getting cleared not enough?
// Hangs on 2 single block writes in a row otherwise.
while((regs->sd_status & (1u<<29 | STATUS_CMD_BUSY)) != 1u<<29);
setPort(regs, port);
regs->sd_blockcount = port->blocks;
// sd_blockcount32 doesn't need to be set for the 32 bit FIFO to work.
regs->sd_stop = ((cmd & CMD_MBT) ? STOP_AUTO_STOP : 0); // TODO: Works with SDIO?
regs->sd_arg = arg;
regs->sd_fifo32_cnt = ((cmd & CMD_DIR_R) ? FIFO32_FULL_IE : FIFO32_NOT_EMPTY_IE) |
FIFO32_CLEAR | FIFO32_EN;
regs->sd_cmd = cmd; // Start
u32 *buf = port->buf;
// Check for data transfer and if buf is NULL (NULL means DMA).
if((cmd & CMD_DT) && (buf != NULL))
doCpuTransfer(regs, cmd, buf);
// On multi-block read transfer response end fires
// while reading the last block from FIFO
// so we need to check before __wfi().
while(!(regs->sd_status & STATUS_RESP_END)) __wfi();
getResponse(regs, port, cmd);
const u32 status = regs->sd_status & STATUS_MASK_ERR;
regs->sd_status = STATUS_CMD_BUSY; // Acknowledge all but CMD busy.
return status;
}
#if (_3DS && ARM11)
#include "arm11/fmt.h"
void TOSHSD_dbgPrint(ToshsdPort *const port)
{
Toshsd *const regs = getToshsdRegs(port->portNum / 2u); // TODO: gcc generates a udf instruction for this line.
ee_printf("Toshsd last cmd: %u\n"
" sd_status: 0x%lX\n"
" sd_err_status: 0x%lX\n",
regs->sd_cmd & 0xFFu,
regs->sd_status,
regs->sd_err_status);
ee_printf(" sd_portsel: 0x%X\n"
" sd_clk_ctrl: 0x%X\n"
" sd_option: 0x%X\n"
" resp: ",
regs->sd_portsel,
regs->sd_clk_ctrl,
regs->sd_option);
for(u32 i = 0; i < 4; i++) ee_printf("%08lX ", port->resp[i]);
ee_puts("");
}
#endif // #if (_3DS && ARM11)