mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-08 23:04:13 +08:00
575 lines
19 KiB
C
575 lines
19 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 <string.h>
|
||
#include "drivers/mmc/sdmmc.h"
|
||
#include "drivers/toshsd.h"
|
||
#include "drivers/toshsd_config.h"
|
||
#ifdef _3DS
|
||
#ifdef ARM9
|
||
#include "arm9/drivers/timer.h"
|
||
#include "util.h" // wait_cycles()
|
||
#elif ARM11
|
||
#include "arm11/drivers/timer.h"
|
||
#endif // #ifdef ARM9
|
||
#elif TWL
|
||
// TODO
|
||
#endif // #ifdef _3DS
|
||
#include "drivers/mmc/sd_spec.h"
|
||
#include "drivers/mmc/mmc_spec.h"
|
||
|
||
|
||
// Note on INIT_CLOCK:
|
||
// 400 kHz is allowed by the specs. 523 kHz has been proven to work reliably
|
||
// for SD cards and eMMC but very early MMCs can fail at init.
|
||
// We lose about 5 ms of time on init by using 261 kHz.
|
||
#ifdef _3DS
|
||
#ifdef ARM9
|
||
#define DELAY_MULT (1u) // Assumes ARM9 timer. Same speed as controller.
|
||
#elif ARM11
|
||
#define DELAY_MULT (2u) // Assumes ARM11 timer. 2x controller speed.
|
||
#endif // #ifdef ARM9
|
||
|
||
#define INIT_CLOCK (1u<<6) // 261 kHz
|
||
#define INIT_DELAY (DELAY_MULT * 256 * 74)
|
||
|
||
#define SDR12_CLOCK (1u) // 16.756991 MHz
|
||
#define SDR25_CLOCK (0u) // 33.513982 MHz
|
||
|
||
#elif TWL
|
||
|
||
#define INIT_CLOCK (1u<<5) // 261 kHz
|
||
#define INIT_DELAY (1u * 128 * 74) // Assumes ARM9 timers. Same speed as controller.
|
||
|
||
#define SDR12_CLOCK (0u) // 16.756991 MHz
|
||
#endif // #ifdef _3DS
|
||
|
||
|
||
#define IF_COND_ARG (SD_CMD8_VHS_2_7_3_6V | SD_CMD8_CHK_PATT)
|
||
#define SD_OP_COND_ARG (SD_ACMD41_XPC | SD_OCR_3_2_3_3V) // We support 150 mA and 3.3V. Without HCS bit.
|
||
#define MMC_OP_COND_ARG (/*MMC_OCR_SECT_MODE |*/ MMC_OCR_3_2_3_3V) // We support s̶e̶c̶t̶o̶r̶ a̶d̶r̶e̶s̶s̶i̶n̶g̶ a̶n̶d̶ 3.3V.
|
||
#define SD_OCR_VOLT_MASK (SD_OCR_3_2_3_3V) // We support 3.3V only.
|
||
#define MMC_OCR_VOLT_MASK (MMC_OCR_3_2_3_3V) // We support 3.3V only.
|
||
|
||
enum
|
||
{
|
||
// Card types.
|
||
CTYPE_NONE = 0u, // Unitialized/no card.
|
||
CTYPE_SDSC = 1u, // SDSC.
|
||
CTYPE_SDHC = 2u, // SDHC, SDXC.
|
||
CTYPE_SDUC = 3u, // SDUC.
|
||
CTYPE_MMC = 4u, // (e)MMC.
|
||
CTYPE_MMCHC = 5u // High capacity (e)MMC (>2 GB).
|
||
};
|
||
|
||
|
||
typedef struct
|
||
{
|
||
ToshsdPort port;
|
||
u8 cardType;
|
||
u8 spec_vers; // (e)MMC only SPEC_VERS from CSD. 0 for SD.
|
||
u16 rca; // Relative Card Address (RCA).
|
||
u16 ccc; // SD/(e)MMC command class support from CSD. One per bit starting at 0.
|
||
u32 sectors; // Size in 512 byte units.
|
||
|
||
// Cached card infos.
|
||
u32 cid[4]; // Raw CID with the CRC zeroed out.
|
||
} SdmmcDev;
|
||
|
||
SdmmcDev g_devs[2] = {0};
|
||
|
||
|
||
|
||
/*static u32 sendCardStatus(ToshsdPort *const port, u32 rca, u32 *const statusOut)
|
||
{
|
||
// Same CMD for SD/(e)MMC but the argument format differs slightly.
|
||
const u32 res = TOSHSD_sendCommand(port, MMC_SEND_STATUS, rca);
|
||
if(res == 0) *statusOut = port->resp[0];
|
||
|
||
return res;
|
||
}*/
|
||
|
||
static u32 sdSendAppCmd(ToshsdPort *const port, u16 cmd, u32 arg, u32 rca)
|
||
{
|
||
u32 res = TOSHSD_sendCommand(port, SD_APP_CMD, rca); // TODO: How do we handle the R1 response?
|
||
if(res == 0)
|
||
{
|
||
res = TOSHSD_sendCommand(port, cmd, arg);
|
||
}
|
||
|
||
return res;
|
||
}
|
||
|
||
static u32 goIdleState(ToshsdPort *const port)
|
||
{
|
||
// Enter idle state before we start the init procedure.
|
||
// Works from all but inactive state. CMD is the same for SD/(e)MMC.
|
||
// For (e)MMC there are optional init paths:
|
||
// arg = 0x00000000 -> GO_IDLE_STATE.
|
||
// arg = 0xF0F0F0F0 -> GO_PRE_IDLE_STATE.
|
||
// arg = 0xFFFFFFFA -> BOOT_INITIATION.
|
||
u32 res = TOSHSD_sendCommand(port, MMC_GO_IDLE_STATE, 0);
|
||
if(res != 0) return SDMMC_ERR_GO_IDLE_STATE;
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
static u32 initIdleState(ToshsdPort *const port, u8 *const cardTypeOut)
|
||
{
|
||
// Tell the card what interfaces and voltages we support.
|
||
// Only SD v2 and up will respond. (e)MMC won't respond.
|
||
u32 res = TOSHSD_sendCommand(port, SD_SEND_IF_COND, IF_COND_ARG);
|
||
if(res == 0)
|
||
{
|
||
// If the card supports the interfaces and voltages
|
||
// it should echo back the check pattern and set the
|
||
// support bits.
|
||
// Since we don't support anything but the
|
||
// standard SD interface at 3.3V we can check
|
||
// the whole response at once.
|
||
if(port->resp[0] != IF_COND_ARG) return SDMMC_ERR_IF_COND_RESP;
|
||
}
|
||
else if(res != TSD_ERR_CMD_TMOUT) return SDMMC_ERR_SEND_IF_COND; // Card responded but an error occured.
|
||
|
||
// Send the first app CMD. If this times out it's (e)MMC.
|
||
// If SEND_IF_COND timed out tell the SD card we are a v1 host.
|
||
const u32 opCondArg = SD_OP_COND_ARG | (res<<8 ^ SD_ACMD41_HCS); // Caution! Controller specific hack.
|
||
u8 cardType = CTYPE_SDSC;
|
||
res = sdSendAppCmd(port, SD_APP_SD_SEND_OP_COND, opCondArg, 0);
|
||
if(res != 0)
|
||
{
|
||
if(res == TSD_ERR_CMD_TMOUT) cardType = CTYPE_MMC; // Continue with (e)MMC init.
|
||
else return SDMMC_ERR_SEND_OP_COND; // Unknown error.
|
||
}
|
||
|
||
if(cardType == CTYPE_SDSC) // SD card.
|
||
{
|
||
// Loop until a timeout of 1 second or the card is ready.
|
||
u32 tries = 199; // 200 tries minus the first one.
|
||
u32 ocr;
|
||
do
|
||
{
|
||
// Linux uses 10 ms but the card doesn't become ready faster
|
||
// when polling with delay. Use 5 ms as compromise so not much
|
||
// time is wasted when the card becomes ready in the middle of the delay.
|
||
TIMER_sleepMs(5);
|
||
|
||
res = sdSendAppCmd(port, SD_APP_SD_SEND_OP_COND, opCondArg, 0);
|
||
if(res != 0) return SDMMC_ERR_SEND_OP_COND;
|
||
|
||
ocr = port->resp[0];
|
||
} while(--tries && !(ocr & SD_OCR_NOT_BUSY));
|
||
|
||
// SD card didn't finish init within 1 second.
|
||
if(tries == 0) return SDMMC_ERR_OP_COND_TMOUT;
|
||
|
||
// TODO: From sd.c in Linux:
|
||
// "Some SD cards claims an out of spec VDD voltage range.
|
||
// Let's treat these bits as being in-valid and especially also bit7."
|
||
if(!(ocr & SD_OCR_VOLT_MASK)) return SDMMC_ERR_VOLT_SUPPORT;
|
||
if(ocr & SD_OCR_CCS) cardType = CTYPE_SDHC;
|
||
}
|
||
else // (e)MMC
|
||
{
|
||
// Loop until a timeout of 1 second or the card is ready.
|
||
u32 tries = 200;
|
||
u32 ocr;
|
||
do
|
||
{
|
||
res = TOSHSD_sendCommand(port, MMC_SEND_OP_COND, MMC_OP_COND_ARG);
|
||
if(res != 0) return SDMMC_ERR_SEND_OP_COND;
|
||
|
||
ocr = port->resp[0];
|
||
if(!--tries || (ocr & MMC_OCR_NOT_BUSY)) break;
|
||
|
||
// Linux uses 10 ms but the card doesn't become ready faster
|
||
// when polling with delay. Use 5 ms as compromise so not much
|
||
// time is wasted when the card becomes ready in the middle of the delay.
|
||
TIMER_sleepMs(5);
|
||
} while(1);
|
||
|
||
// (e)MMC didn't finish init within 1 second.
|
||
if(tries == 0) return SDMMC_ERR_OP_COND_TMOUT;
|
||
|
||
// Check if the (e)MMC supports the voltage and if it's high capacity.
|
||
if(!(ocr & MMC_OCR_VOLT_MASK)) return SDMMC_ERR_VOLT_SUPPORT; // Voltage not supported.
|
||
// TODO: High capacity (e)MMC check.
|
||
}
|
||
|
||
*cardTypeOut = cardType;
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
static u32 initReadyState(SdmmcDev *const dev)
|
||
{
|
||
ToshsdPort *const port = &dev->port;
|
||
|
||
// SD card voltage switch sequence goes here if supported.
|
||
|
||
// Get the CID. CMD is the same for SD/(e)MMC.
|
||
u32 res = TOSHSD_sendCommand(port, MMC_ALL_SEND_CID, 0);
|
||
if(res != 0) return SDMMC_ERR_ALL_SEND_CID;
|
||
memcpy(dev->cid, port->resp, 16);
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
static u32 initIdentState(SdmmcDev *const dev, const u8 cardType, u32 *const rcaOut)
|
||
{
|
||
ToshsdPort *const port = &dev->port;
|
||
|
||
u32 rca;
|
||
if(cardType < CTYPE_MMC)
|
||
{
|
||
// Ask the SD card to send its RCA.
|
||
u32 res = TOSHSD_sendCommand(port, SD_SEND_RELATIVE_ADDR, 0);
|
||
if(res != 0) return SDMMC_ERR_SET_SEND_RCA;
|
||
|
||
rca = port->resp[0]>>16; // RCA in upper 16 bits.
|
||
}
|
||
else
|
||
{
|
||
// Set the RCA of the (e)MMC to 1. 0 is reserved.
|
||
// A few extremely old, unbranded (but Nokia?) MMC's will time
|
||
// out here for unknown reason. They won't work on DSi anyway (FAT12).
|
||
// The RCA is in the upper 16 bits of the argument.
|
||
u32 res = TOSHSD_sendCommand(port, MMC_SET_RELATIVE_ADDR, 1u<<16); // TODO: Should we check the R1 response?
|
||
if(res != 0) return SDMMC_ERR_SET_SEND_RCA;
|
||
|
||
rca = 1;
|
||
}
|
||
|
||
dev->rca = rca;
|
||
*rcaOut = rca<<16;
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
// Based on code from linux/drivers/mmc/core/sd.c.
|
||
// Works only with u32[4] buffer.
|
||
#define UNSTUFF_BITS(resp, start, size) \
|
||
({ \
|
||
const u32 __size = size; \
|
||
const u32 __mask = (__size < 32 ? 1u<<__size : 0u) - 1; \
|
||
const u32 __off = 3 - ((start) / 32u); \
|
||
const u32 __shift = (start) & 31u; \
|
||
u32 __res; \
|
||
\
|
||
__res = resp[__off]>>__shift; \
|
||
if(__size + __shift > 32) \
|
||
__res |= resp[__off - 1]<<((32 - __shift) % 32u); \
|
||
__res & __mask; \
|
||
})
|
||
|
||
static void parseCsd(SdmmcDev *const dev, const u8 cardType)
|
||
{
|
||
// Note: The MSBs are in csd[0].
|
||
const u32 *const csd = dev->port.resp;
|
||
|
||
// structure = 0 is CSD version 1.0.
|
||
const u8 structure = UNSTUFF_BITS(csd, 126, 2); // [127:126]
|
||
dev->spec_vers = UNSTUFF_BITS(csd, 122, 4); // [125:122] All 0 for SD cards.
|
||
u32 sectors;
|
||
if(structure == 0 || cardType == CTYPE_MMC)
|
||
{
|
||
// Same calculation for SDSC and (e)MMC <=2 GB.
|
||
// TODO: https://github.com/torvalds/linux/blob/master/drivers/mmc/core/sd.c#L129
|
||
// This doesn't work? Always calculates half of the expected sectors.
|
||
const u32 read_bl_len = UNSTUFF_BITS(csd, 80, 4); // [83:80]
|
||
const u32 c_size = UNSTUFF_BITS(csd, 62, 12); // [73:62]
|
||
const u32 c_size_mult = UNSTUFF_BITS(csd, 47, 3); // [49:47]
|
||
|
||
// Note: READ_BL_LEN is at least 9.
|
||
// Slightly modified to calculate sectors instead of bytes.
|
||
sectors = (c_size + 1) * (1u<<(c_size_mult + 2)) * (1u<<(read_bl_len - 9));
|
||
}
|
||
else
|
||
{
|
||
// SD CSD version 3.0 format.
|
||
// For version 2.0 this is 22 bits however the uppe bits
|
||
// are reserved and zero filled so this is fine.
|
||
const u32 c_size = UNSTUFF_BITS(csd, 48, 28); // [75:48]
|
||
|
||
sectors = (c_size + 1) * 1024u;
|
||
}
|
||
// TODO: High capacity (e)MMC encodes the size in the ext CSD.
|
||
dev->sectors = sectors;
|
||
|
||
dev->ccc = UNSTUFF_BITS(csd, 84, 12); // [95:84]
|
||
}
|
||
|
||
static u32 initStandbyState(SdmmcDev *const dev, const u8 cardType, const u32 rca)
|
||
{
|
||
ToshsdPort *const port = &dev->port;
|
||
|
||
// Get the CSD. CMD is the same for SD/(e)MMC.
|
||
u32 res = TOSHSD_sendCommand(port, MMC_SEND_CSD, rca);
|
||
if(res != 0) return SDMMC_ERR_SEND_CSD;
|
||
parseCsd(dev, cardType);
|
||
|
||
// Select card and switch to transfer state.
|
||
const u16 selCardCmd = (cardType < CTYPE_MMC ? SD_SELECT_CARD : MMC_SELECT_CARD);
|
||
res = TOSHSD_sendCommand(port, selCardCmd, rca); // TODO: Should we check the R1 response?
|
||
if(res != 0) return SDMMC_ERR_SELECT_CARD;
|
||
|
||
// The SD card spec mentions that we should check the lock bit in the
|
||
// response to CMD7 to identify cards requiring a password
|
||
// to unlock which we don't support. Same seems to apply for (e)MMC.
|
||
// Same bit for SD/(e)MMC R1 card status.
|
||
if(port->resp[0] & MMC_R1_CARD_IS_LOCKED)
|
||
return SDMMC_ERR_LOCKED;
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
static u32 initTranState(SdmmcDev *const dev, const u8 cardType, const u32 rca)
|
||
{
|
||
ToshsdPort *const port = &dev->port;
|
||
|
||
if(cardType < CTYPE_MMC)
|
||
{
|
||
// Remove DAT3 pull-up.
|
||
u32 res = sdSendAppCmd(port, SD_APP_SET_CLR_CARD_DETECT, 0, rca); // arg = 0 removes the pull-up.
|
||
if(res != 0) return SDMMC_ERR_SET_CLR_CD;
|
||
|
||
// Switch to 4 bit bus mode.
|
||
res = sdSendAppCmd(port, SD_APP_SET_BUS_WIDTH, 2, rca); // arg = 2 is 4 bit bus width.
|
||
if(res != 0) return SDMMC_ERR_SET_BUS_WIDTH;
|
||
TOSHSD_setBusWidth(port, 4);
|
||
|
||
#ifndef TWL
|
||
// TODO: Is it faster to double the clock earlier or to run this CMD with 4 bit bus width?
|
||
if(dev->ccc & 1u<<10) // Class 10 command support.
|
||
{
|
||
TOSHSD_setBlockLen(port, 64);
|
||
alignas(4) u8 switchStat[64]; // MSB first and big endian.
|
||
TOSHSD_setBuffer(port, (u32*)switchStat, 1);
|
||
const u32 arg = SD_SWITCH_FUNC_ARG(1, 0xF, 0xF, 0xF, 1);
|
||
res = TOSHSD_sendCommand(port, SD_SWITCH_FUNC, arg);
|
||
if(res != 0) return SDMMC_ERR_SWITCH_HS;
|
||
TOSHSD_setBlockLen(port, 512);
|
||
|
||
// [415:400] Support Bits of Functions in Function Group 1.
|
||
if(switchStat[63 - 400 / 8] & 1u<<1) // Is group 1, function 1 "SDR25" supported?
|
||
{
|
||
// SDR25 (50 MHz) supported. Switch to highest supported clock.
|
||
// Stop clock at idle. 33 MHz.
|
||
TOSHSD_setClock(port, (1u<<9) | (1u<<8) | SDR25_CLOCK);
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
// Very old 1 bit bus MMC will time out and set the SWITCH_ERROR bit
|
||
// for these CMDs. Only try with (e)MMC spec >4.0.
|
||
if(dev->spec_vers >= 4) // Version 4.1–4.2–4.3 or higher.
|
||
{
|
||
// Switch to 4 bit bus mode.
|
||
u32 arg = MMC_SWITCH_ARG(MMC_SWITCH_ACC_WR_BYTE, 183, 1, 0);
|
||
u32 res = TOSHSD_sendCommand(port, MMC_SWITCH, arg);
|
||
if(res != 0) return SDMMC_ERR_SET_BUS_WIDTH;
|
||
TOSHSD_setBusWidth(port, 4);
|
||
|
||
#ifndef TWL
|
||
// Switch to high speed timing (52 MHz).
|
||
arg = MMC_SWITCH_ARG(MMC_SWITCH_ACC_WR_BYTE, 185, 1, 0);
|
||
res = TOSHSD_sendCommand(port, MMC_SWITCH, arg);
|
||
if(res != 0) return SDMMC_ERR_SWITCH_HS;
|
||
// Stop clock at idle. 33 MHz.
|
||
TOSHSD_setClock(port, (1u<<9) | (1u<<8) | SDR25_CLOCK);
|
||
#endif
|
||
|
||
// We also should check in the ext CSD the power budget for the card.
|
||
// Nintendo seems to leave it on default (no change).
|
||
}
|
||
}
|
||
|
||
// SD: The description for CMD SET_BLOCKLEN says 512 bytes is the default.
|
||
// (e)MMC: The description for READ_BL_LEN (CSD) says 512 bytes is the default.
|
||
// So it's not required to set the block length?
|
||
//u32 res = TOSHSD_sendCommand(port, MMC_SET_BLOCKLEN, 512);
|
||
//if(res != 0) return SDMMC_ERR_SET_BLOCKLEN;
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
static inline u8 dev2portNum(u8 devNum)
|
||
{
|
||
return (devNum == SDMMC_DEV_eMMC ? TOSHSD_eMMC_PORT : TOSHSD_SLOT_PORT);
|
||
}
|
||
|
||
// TODO: In many places we also want to check the card's response.
|
||
u32 SDMMC_init(u8 devNum)
|
||
{
|
||
if(devNum > SDMMC_DEV_eMMC) return SDMMC_ERR_INVAL_PARAM;
|
||
|
||
SdmmcDev *const dev = &g_devs[devNum];
|
||
ToshsdPort *const port = &dev->port;
|
||
|
||
if(dev->cardType != CTYPE_NONE) return SDMMC_ERR_INITIALIZED;
|
||
|
||
// TODO: When does the card detection timer start? Does not restart on controller reset.
|
||
TOSHSD_initPort(port, dev2portNum(devNum));
|
||
TOSHSD_setClock(port, (1u<<8) | INIT_CLOCK); // Continuous clock, 261/523 kHz.
|
||
#ifdef _3DS
|
||
#ifdef ARM9
|
||
// TODO: Use a timer instead? The delay is only a few hundred us though.
|
||
wait_cycles(2 * INIT_DELAY); // CPU is 2x timer freqency.
|
||
#elif ARM11
|
||
// TODO: Is it worth using a timer? The delay is only a few hundred us.
|
||
TIMER_sleepTicks(INIT_DELAY);
|
||
#endif // #ifdef ARM9
|
||
#elif TWL
|
||
#error "SD/MMC necessary delay unimplemented."
|
||
#endif // #ifdef _3DS
|
||
|
||
u32 res = goIdleState(port);
|
||
if(res != 0) return res;
|
||
|
||
// SD/(e)MMC now in idle state (idle).
|
||
u8 cardType;
|
||
res = initIdleState(port, &cardType);
|
||
if(res != 0) return res;
|
||
|
||
// Stop clock at idle. 261/523 kHz.
|
||
TOSHSD_setClock(port, (1u<<9) | (1u<<8) | INIT_CLOCK);
|
||
|
||
// SD/(e)MMC now in ready state (ready).
|
||
res = initReadyState(dev);
|
||
if(res != 0) return res;
|
||
|
||
// SD/(e)MMC now in identification state (ident).
|
||
u32 rca;
|
||
res = initIdentState(dev, cardType, &rca);
|
||
if(res != 0) return res;
|
||
|
||
// Maximum at this point would be 25 MHz for SD and 20 for (e)MMC.
|
||
// SD: We can increase the clock after end of identification state.
|
||
// TODO: eMMC spec section 7.6
|
||
// "Until the contents of the CSD register is known by the host,
|
||
// the fPP clock rate must remain at fOD. (See Section 12.7 on page 176.)"
|
||
// Since the absolute minimum clock rate is 20 MHz and we are in push-pull
|
||
// mode already can we cheat and switch to 16 MHz before getting the CSD?
|
||
// Note: This seems to be working just fine in all tests.
|
||
// Stop clock at idle. 16 MHz.
|
||
TOSHSD_setClock(port, (1u<<9) | (1u<<8) | SDR12_CLOCK);
|
||
|
||
// SD/(e)MMC now in stand-by state (stby).
|
||
res = initStandbyState(dev, cardType, rca);
|
||
if(res != 0) return res;
|
||
|
||
// SD/(e)MMC now in transfer state (tran).
|
||
res = initTranState(dev, cardType, rca);
|
||
if(res != 0) return res;
|
||
|
||
dev->cardType = cardType;
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
// TODO: Is there any "best practice" way of deinitializing cards?
|
||
// Kick the card back into idle state maybe?
|
||
// Linux seems to deselect cards on "suspend".
|
||
u32 SDMMC_deinit(u8 devNum)
|
||
{
|
||
if(devNum > SDMMC_DEV_eMMC) return SDMMC_ERR_INVAL_PARAM;
|
||
|
||
g_devs[devNum].cardType = CTYPE_NONE;
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
void SDMMC_getCardInfo(u8 devNum, SdmmcInfo *const infoOut)
|
||
{
|
||
if(devNum > SDMMC_DEV_eMMC) return;
|
||
|
||
const SdmmcDev *const dev = &g_devs[devNum];
|
||
const ToshsdPort *const port = &dev->port;
|
||
|
||
infoOut->type = dev->cardType;
|
||
infoOut->spec_vers = dev->spec_vers;
|
||
infoOut->rca = dev->rca;
|
||
infoOut->sectors = dev->sectors;
|
||
const u32 clkSetting = port->sd_clk_ctrl & 0xFFu;
|
||
infoOut->clock = TOSHSD_HCLK / (clkSetting ? clkSetting<<2 : 2u);
|
||
memcpy(infoOut->cid, dev->cid, 16);
|
||
infoOut->ccc = dev->ccc;
|
||
infoOut->busWidth = (port->sd_option & 1u<<15 ? 1u : 4u);
|
||
}
|
||
|
||
u32 SDMMC_getCid(u8 devNum, u32 *const cidOut)
|
||
{
|
||
if(devNum > SDMMC_DEV_eMMC) return SDMMC_ERR_INVAL_PARAM;
|
||
|
||
if(cidOut != NULL) memcpy(cidOut, g_devs[devNum].cid, 16);
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
u32 SDMMC_getSectors(u8 devNum)
|
||
{
|
||
if(devNum > SDMMC_DEV_eMMC) return 0;
|
||
|
||
return g_devs[devNum].sectors;
|
||
}
|
||
|
||
u32 SDMMC_readSectors(u8 devNum, u32 sect, u32 *const buf, u16 count)
|
||
{
|
||
if(devNum > SDMMC_DEV_eMMC || count == 0) return SDMMC_ERR_INVAL_PARAM;
|
||
|
||
SdmmcDev *const dev = &g_devs[devNum];
|
||
const u8 cardType = dev->cardType;
|
||
if(cardType == CTYPE_NONE) return SDMMC_ERR_NO_CARD;
|
||
|
||
ToshsdPort *const port = &dev->port;
|
||
TOSHSD_setBuffer(port, buf, count);
|
||
|
||
if(cardType == CTYPE_SDSC || cardType == CTYPE_MMC) sect *= 512;
|
||
// Read a single 512 bytes block. Same CMD for SD/(e)MMC.
|
||
// Read multiple 512 byte blocks. Same CMD for SD/(e)MMC.
|
||
const u16 cmd = (count == 1 ? MMC_READ_SINGLE_BLOCK : MMC_READ_MULTIPLE_BLOCK);
|
||
const u32 res = TOSHSD_sendCommand(port, cmd, sect);
|
||
if(res != 0) return SDMMC_ERR_SECT_RW; // TODO: In case of errors check the card status.
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|
||
|
||
u32 SDMMC_writeSectors(u8 devNum, u32 sect, const u32 *const buf, u16 count)
|
||
{
|
||
if(devNum > SDMMC_DEV_eMMC || count == 0) return SDMMC_ERR_INVAL_PARAM;
|
||
|
||
SdmmcDev *const dev = &g_devs[devNum];
|
||
const u8 cardType = dev->cardType;
|
||
if(cardType == CTYPE_NONE) return SDMMC_ERR_NO_CARD;
|
||
|
||
ToshsdPort *const port = &dev->port;
|
||
if(!TOSHSD_cardSliderUnlocked()) return SDMMC_ERR_WRITE_PROT; // TODO: Don't do this check for eMMC.
|
||
TOSHSD_setBuffer(port, (u32*)buf, count);
|
||
|
||
if(cardType == CTYPE_SDSC || cardType == CTYPE_MMC) sect *= 512;
|
||
// Write a single 512 bytes block. Same CMD for SD/(e)MMC.
|
||
// Write multiple 512 byte blocks. Same CMD for SD/(e)MMC.
|
||
const u16 cmd = (count == 1 ? MMC_WRITE_BLOCK : MMC_WRITE_MULTIPLE_BLOCK);
|
||
const u32 res = TOSHSD_sendCommand(port, cmd, sect);
|
||
if(res != 0) return SDMMC_ERR_SECT_RW; // TODO: In case of errors check the card status.
|
||
|
||
return SDMMC_ERR_NONE;
|
||
}
|