mirror of
https://github.com/CTCaer/hekate.git
synced 2024-11-26 11:42:09 +00:00
883 lines
23 KiB
C
Executable file
883 lines
23 KiB
C
Executable file
/*
|
|
* Copyright (c) 2018 naehrwert
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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 "sdmmc.h"
|
|
#include "mmc.h"
|
|
#include "sd.h"
|
|
#include "util.h"
|
|
#include "heap.h"
|
|
|
|
/*#include "gfx.h"
|
|
extern gfx_ctxt_t gfx_ctxt;
|
|
extern gfx_con_t gfx_con;
|
|
#define DPRINTF(...) gfx_printf(&gfx_con, __VA_ARGS__)*/
|
|
#define DPRINTF(...)
|
|
|
|
static inline u32 unstuff_bits(u32 *resp, u32 start, u32 size)
|
|
{
|
|
const u32 mask = (size < 32 ? 1 << size : 0) - 1;
|
|
const u32 off = 3 - ((start) / 32);
|
|
const u32 shft = (start) & 31;
|
|
u32 res = resp[off] >> shft;
|
|
if (size + shft > 32)
|
|
res |= resp[off - 1] << ((32 - shft) % 32);
|
|
return res & mask;
|
|
}
|
|
|
|
/*
|
|
* Common functions for SD and MMC.
|
|
*/
|
|
|
|
static int _sdmmc_storage_check_result(u32 res)
|
|
{
|
|
//Error mask:
|
|
//R1_OUT_OF_RANGE, R1_ADDRESS_ERROR, R1_BLOCK_LEN_ERROR,
|
|
//R1_ERASE_SEQ_ERROR, R1_ERASE_PARAM, R1_WP_VIOLATION,
|
|
//R1_LOCK_UNLOCK_FAILED, R1_COM_CRC_ERROR, R1_ILLEGAL_COMMAND,
|
|
//R1_CARD_ECC_FAILED, R1_CC_ERROR, R1_ERROR, R1_CID_CSD_OVERWRITE,
|
|
//R1_WP_ERASE_SKIP, R1_ERASE_RESET, R1_SWITCH_ERROR
|
|
if (!(res & 0xFDF9A080))
|
|
return 1;
|
|
//TODO: R1_SWITCH_ERROR we can skip for certain card types.
|
|
return 0;
|
|
}
|
|
|
|
static int _sdmmc_storage_execute_cmd_type1_ex(sdmmc_storage_t *storage, u32 *resp, u32 cmd, u32 arg, u32 check_busy, u32 expected_state, u32 mask)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, cmd, arg, SDMMC_RSP_TYPE_1, check_busy);
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, 0, 0))
|
|
return 0;
|
|
|
|
sdmmc_get_rsp(storage->sdmmc, resp, 4, SDMMC_RSP_TYPE_1);
|
|
if (mask)
|
|
*resp &= ~mask;
|
|
|
|
if (_sdmmc_storage_check_result(*resp))
|
|
if (expected_state == 0x10 || R1_CURRENT_STATE(*resp) == expected_state)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int _sdmmc_storage_execute_cmd_type1(sdmmc_storage_t *storage, u32 cmd, u32 arg, u32 check_busy, u32 expected_state)
|
|
{
|
|
u32 tmp;
|
|
return _sdmmc_storage_execute_cmd_type1_ex(storage, &tmp, cmd, arg, check_busy, expected_state, 0);
|
|
}
|
|
|
|
static int _sdmmc_storage_go_idle_state(sdmmc_storage_t *storage)
|
|
{
|
|
sdmmc_cmd_t cmd;
|
|
sdmmc_init_cmd(&cmd, MMC_GO_IDLE_STATE, 0, SDMMC_RSP_TYPE_0, 0);
|
|
return sdmmc_execute_cmd(storage->sdmmc, &cmd, 0, 0);
|
|
}
|
|
|
|
static int _sdmmc_storage_get_cid(sdmmc_storage_t *storage, void *buf)
|
|
{
|
|
sdmmc_cmd_t cmd;
|
|
sdmmc_init_cmd(&cmd, MMC_ALL_SEND_CID, 0, SDMMC_RSP_TYPE_2, 0);
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmd, 0, 0))
|
|
return 0;
|
|
sdmmc_get_rsp(storage->sdmmc, buf, 0x10, SDMMC_RSP_TYPE_2);
|
|
return 1;
|
|
}
|
|
|
|
static int _sdmmc_storage_select_card(sdmmc_storage_t *storage)
|
|
{
|
|
return _sdmmc_storage_execute_cmd_type1(storage, MMC_SELECT_CARD, storage->rca << 16, 1, 0x10);
|
|
}
|
|
|
|
static int _sdmmc_storage_get_csd(sdmmc_storage_t *storage, void *buf)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, MMC_SEND_CSD, storage->rca << 16, SDMMC_RSP_TYPE_2, 0);
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, 0, 0))
|
|
return 0;
|
|
sdmmc_get_rsp(storage->sdmmc, buf, 0x10, SDMMC_RSP_TYPE_2);
|
|
return 1;
|
|
}
|
|
|
|
static int _sdmmc_storage_set_blocklen(sdmmc_storage_t *storage, u32 blocklen)
|
|
{
|
|
return _sdmmc_storage_execute_cmd_type1(storage, MMC_SET_BLOCKLEN, blocklen, 0, R1_STATE_TRAN);
|
|
}
|
|
|
|
static int _sdmmc_storage_get_status(sdmmc_storage_t *storage, u32 *resp, u32 mask)
|
|
{
|
|
return _sdmmc_storage_execute_cmd_type1_ex(storage, resp, MMC_SEND_STATUS, storage->rca << 16, 0, R1_STATE_TRAN, mask);
|
|
}
|
|
|
|
static int _sdmmc_storage_check_status(sdmmc_storage_t *storage)
|
|
{
|
|
u32 tmp;
|
|
return _sdmmc_storage_get_status(storage, &tmp, 0);
|
|
}
|
|
|
|
static int _sdmmc_storage_readwrite_ex(sdmmc_storage_t *storage, u32 *blkcnt_out, u32 sector, u32 num_sectors, void *buf, u32 is_write)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, is_write ? MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK, sector, SDMMC_RSP_TYPE_1, 0);
|
|
|
|
sdmmc_req_t reqbuf;
|
|
reqbuf.buf = buf;
|
|
reqbuf.num_sectors = num_sectors;
|
|
reqbuf.blksize = 512;
|
|
reqbuf.is_write = is_write;
|
|
reqbuf.is_multi_block = 1;
|
|
reqbuf.is_auto_cmd12 = 1;
|
|
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, blkcnt_out))
|
|
{
|
|
u32 tmp = 0;
|
|
sdmmc_stop_transmission(storage->sdmmc, &tmp);
|
|
_sdmmc_storage_get_status(storage, &tmp, 0);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int sdmmc_storage_end(sdmmc_storage_t *storage)
|
|
{
|
|
if (!_sdmmc_storage_go_idle_state(storage))
|
|
return 0;
|
|
sdmmc_end(storage->sdmmc);
|
|
return 1;
|
|
}
|
|
|
|
static int _sdmmc_storage_readwrite(sdmmc_storage_t *storage, u32 sector, u32 num_sectors, void *buf, u32 is_write)
|
|
{
|
|
u8 *bbuf = (u8 *)buf;
|
|
|
|
while (num_sectors)
|
|
{
|
|
u32 blkcnt = 0;
|
|
//Retry once on error.
|
|
if (!_sdmmc_storage_readwrite_ex(storage, &blkcnt, sector, MIN(num_sectors, 0xFFFF), bbuf, is_write))
|
|
if (!_sdmmc_storage_readwrite_ex(storage, &blkcnt, sector, MIN(num_sectors, 0xFFFF), bbuf, is_write))
|
|
return 0;
|
|
DPRINTF("readwrite: %08X\n", blkcnt);
|
|
sector += blkcnt;
|
|
num_sectors -= blkcnt;
|
|
bbuf += 512 * blkcnt;
|
|
}
|
|
}
|
|
|
|
int sdmmc_storage_read(sdmmc_storage_t *storage, u32 sector, u32 num_sectors, void *buf)
|
|
{
|
|
return _sdmmc_storage_readwrite(storage, sector, num_sectors, buf, 0);
|
|
}
|
|
|
|
int sdmmc_storage_write(sdmmc_storage_t *storage, u32 sector, u32 num_sectors, void *buf)
|
|
{
|
|
return _sdmmc_storage_readwrite(storage, sector, num_sectors, buf, 1);
|
|
}
|
|
|
|
/*
|
|
* MMC specific functions.
|
|
*/
|
|
|
|
static int _mmc_storage_get_op_cond_inner(sdmmc_storage_t *storage, u32 *pout, u32 power)
|
|
{
|
|
sdmmc_cmd_t cmd;
|
|
|
|
u32 arg = 0;
|
|
switch (power)
|
|
{
|
|
case SDMMC_POWER_1_8:
|
|
arg = 0x40000080; //Sector access, voltage.
|
|
break;
|
|
case SDMMC_POWER_3_3:
|
|
arg = 0x403F8000; //Sector access, voltage.
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_init_cmd(&cmd, MMC_SEND_OP_COND, arg, SDMMC_RSP_TYPE_3, 0);
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmd, 0, 0))
|
|
return 0;
|
|
|
|
return sdmmc_get_rsp(storage->sdmmc, pout, 4, SDMMC_RSP_TYPE_3);
|
|
}
|
|
|
|
static int _mmc_storage_get_op_cond(sdmmc_storage_t *storage, u32 power)
|
|
{
|
|
u32 timeout = get_tmr() + 1500000;
|
|
|
|
while (1)
|
|
{
|
|
u32 cond = 0;
|
|
if (!_mmc_storage_get_op_cond_inner(storage, &cond, power))
|
|
break;
|
|
if (cond & 0x80000000)
|
|
{
|
|
if (cond & 0x40000000)
|
|
storage->has_sector_access = 1;
|
|
return 1;
|
|
}
|
|
if (get_tmr() > timeout)
|
|
break;
|
|
sleep(1000);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _mmc_storage_set_relative_addr(sdmmc_storage_t *storage)
|
|
{
|
|
return _sdmmc_storage_execute_cmd_type1(storage, MMC_SET_RELATIVE_ADDR, storage->rca << 16, 0, 0x10);
|
|
}
|
|
|
|
static int _mmc_storage_get_ext_csd(sdmmc_storage_t *storage, void *buf)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, MMC_SEND_EXT_CSD, 0, SDMMC_RSP_TYPE_1, 0);
|
|
|
|
sdmmc_req_t reqbuf;
|
|
reqbuf.buf = buf;
|
|
reqbuf.blksize = 512;
|
|
reqbuf.num_sectors = 1;
|
|
reqbuf.is_write = 0;
|
|
reqbuf.is_multi_block = 0;
|
|
reqbuf.is_auto_cmd12 = 0;
|
|
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, 0))
|
|
return 0;
|
|
|
|
u32 tmp = 0;
|
|
sdmmc_get_rsp(storage->sdmmc, &tmp, 4, SDMMC_RSP_TYPE_1);
|
|
return _sdmmc_storage_check_result(tmp);
|
|
}
|
|
|
|
static int _mmc_storage_switch(sdmmc_storage_t *storage, u32 arg)
|
|
{
|
|
return _sdmmc_storage_execute_cmd_type1(storage, MMC_SWITCH, arg, 1, 0x10);
|
|
}
|
|
|
|
static int _mmc_storage_switch_buswidth(sdmmc_storage_t *storage, u32 bus_width)
|
|
{
|
|
if (bus_width == SDMMC_BUS_WIDTH_1)
|
|
return 1;
|
|
|
|
u32 arg = 0;
|
|
switch (bus_width)
|
|
{
|
|
case SDMMC_BUS_WIDTH_4:
|
|
arg = SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_4);
|
|
break;
|
|
case SDMMC_BUS_WIDTH_8:
|
|
arg = SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_8);
|
|
break;
|
|
}
|
|
|
|
if (_mmc_storage_switch(storage, arg))
|
|
if (_sdmmc_storage_check_status(storage))
|
|
{
|
|
sdmmc_set_bus_width(storage->sdmmc, bus_width);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _mmc_storage_enable_HS(sdmmc_storage_t *storage, int check)
|
|
{
|
|
if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS)))
|
|
return 0;
|
|
if (check && !_sdmmc_storage_check_status(storage))
|
|
return 0;
|
|
if (!sdmmc_setup_clock(storage->sdmmc, 2))
|
|
return 0;
|
|
DPRINTF("[mmc] switched to HS\n");
|
|
if (check || _sdmmc_storage_check_status(storage))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int _mmc_storage_enable_HS200(sdmmc_storage_t *storage)
|
|
{
|
|
if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS200)))
|
|
return 0;
|
|
if (!sdmmc_setup_clock(storage->sdmmc, 3))
|
|
return 0;
|
|
if (!sdmmc_config_tuning(storage->sdmmc, 3, MMC_SEND_TUNING_BLOCK_HS200))
|
|
return 0;
|
|
DPRINTF("[mmc] switched to HS200\n");
|
|
return _sdmmc_storage_check_status(storage);
|
|
}
|
|
|
|
static int _mmc_storage_enable_HS400(sdmmc_storage_t *storage)
|
|
{
|
|
if (!_mmc_storage_enable_HS200(storage))
|
|
return 0;
|
|
sdmmc_get_venclkctl(storage->sdmmc);
|
|
if (!_mmc_storage_enable_HS(storage, 0))
|
|
return 0;
|
|
if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_BUS_WIDTH, EXT_CSD_DDR_BUS_WIDTH_8)))
|
|
return 0;
|
|
if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS400)))
|
|
return 0;
|
|
if (!sdmmc_setup_clock(storage->sdmmc, 4))
|
|
return 0;
|
|
DPRINTF("[mmc] switched to HS400\n");
|
|
return _sdmmc_storage_check_status(storage);
|
|
}
|
|
|
|
static int _mmc_storage_enable_highspeed(sdmmc_storage_t *storage, u32 card_type, u32 type)
|
|
{
|
|
//TODO: this should be a config item.
|
|
//---v
|
|
if (!1 || sdmmc_get_voltage(storage->sdmmc) != SDMMC_POWER_1_8)
|
|
goto out;
|
|
|
|
if (sdmmc_get_bus_width(storage->sdmmc) == SDMMC_BUS_WIDTH_8 &&
|
|
card_type & EXT_CSD_CARD_TYPE_HS400_1_8V &&
|
|
type == 4)
|
|
return _mmc_storage_enable_HS400(storage);
|
|
|
|
if (sdmmc_get_bus_width(storage->sdmmc) == SDMMC_BUS_WIDTH_8 ||
|
|
sdmmc_get_bus_width(storage->sdmmc) == SDMMC_BUS_WIDTH_4
|
|
&& card_type & EXT_CSD_CARD_TYPE_HS200_1_8V
|
|
&& (type == 4 || type == 3))
|
|
return _mmc_storage_enable_HS200(storage);
|
|
|
|
out:;
|
|
if (card_type & EXT_CSD_CARD_TYPE_HS_52)
|
|
return _mmc_storage_enable_HS(storage, 1);
|
|
return 1;
|
|
}
|
|
|
|
static int _mmc_storage_enable_bkops(sdmmc_storage_t *storage)
|
|
{
|
|
if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_SET_BITS, EXT_CSD_BKOPS_EN, EXT_CSD_BKOPS_LEVEL_2)))
|
|
return 0;
|
|
return _sdmmc_storage_check_status(storage);
|
|
}
|
|
|
|
int sdmmc_storage_init_mmc(sdmmc_storage_t *storage, sdmmc_t *sdmmc, u32 id, u32 bus_width, u32 type)
|
|
{
|
|
memset(storage, 0, sizeof(sdmmc_storage_t));
|
|
storage->sdmmc = sdmmc;
|
|
storage->rca = 2; //TODO: this could be a config item.
|
|
|
|
if (!sdmmc_init(sdmmc, id, SDMMC_POWER_1_8, SDMMC_BUS_WIDTH_1, 0, 0))
|
|
return 0;
|
|
DPRINTF("[mmc] after init\n");
|
|
|
|
sleep(1000 + (74000 + sdmmc->divisor - 1) / sdmmc->divisor);
|
|
|
|
if (!_sdmmc_storage_go_idle_state(storage))
|
|
return 0;
|
|
DPRINTF("[mmc] went to idle state\n");
|
|
|
|
if (!_mmc_storage_get_op_cond(storage, SDMMC_POWER_1_8))
|
|
return 0;
|
|
DPRINTF("[mmc] got op cond\n");
|
|
|
|
if (!_sdmmc_storage_get_cid(storage, storage->cid))
|
|
return 0;
|
|
DPRINTF("[mmc] got cid\n");
|
|
|
|
if (!_mmc_storage_set_relative_addr(storage))
|
|
return 0;
|
|
DPRINTF("[mmc] set relative addr\n");
|
|
|
|
if (!_sdmmc_storage_get_csd(storage, storage->csd))
|
|
return 0;
|
|
DPRINTF("[mmc] got csd\n");
|
|
|
|
if (!sdmmc_setup_clock(storage->sdmmc, 1))
|
|
return 0;
|
|
DPRINTF("[mmc] after setup clock\n");
|
|
|
|
if (!_sdmmc_storage_select_card(storage))
|
|
return 0;
|
|
DPRINTF("[mmc] card selected\n");
|
|
|
|
if (!_sdmmc_storage_set_blocklen(storage, 512))
|
|
return 0;
|
|
DPRINTF("[mmc] set blocklen to 512\n");
|
|
|
|
u32 *csd = (u32 *)storage->csd;
|
|
//Check system specification version, only version 4.0 and later support below features.
|
|
if (unstuff_bits(csd, 122, 4) < CSD_SPEC_VER_4)
|
|
{
|
|
storage->sec_cnt = (1 + unstuff_bits(csd, 62, 12)) << (unstuff_bits(csd, 47, 3) + 2);
|
|
return 1;
|
|
}
|
|
|
|
if (!_mmc_storage_switch_buswidth(storage, bus_width))
|
|
return 0;
|
|
DPRINTF("[mmc] switched buswidth\n");
|
|
|
|
u8 *ext_csd = (u8 *)malloc(512);
|
|
if (!_mmc_storage_get_ext_csd(storage, ext_csd))
|
|
{
|
|
free(ext_csd);
|
|
return 0;
|
|
}
|
|
//gfx_hexdump(&gfx_con, 0, ext_csd, 512);
|
|
|
|
storage->sec_cnt = *(u32 *)&ext_csd[EXT_CSD_SEC_CNT];
|
|
|
|
if (storage->cid[0xE] == 0x11 && ext_csd[EXT_CSD_BKOPS_EN] & EXT_CSD_BKOPS_LEVEL_2)
|
|
_mmc_storage_enable_bkops(storage);
|
|
|
|
if (!_mmc_storage_enable_highspeed(storage, ext_csd[EXT_CSD_CARD_TYPE], type))
|
|
{
|
|
free(ext_csd);
|
|
return 0;
|
|
}
|
|
DPRINTF("[mmc] switched to possible highspeed mode\n");
|
|
|
|
sdmmc_sd_clock_ctrl(storage->sdmmc, 1);
|
|
|
|
free(ext_csd);
|
|
return 1;
|
|
}
|
|
|
|
int sdmmc_storage_set_mmc_partition(sdmmc_storage_t *storage, u32 partition)
|
|
{
|
|
if (!_mmc_storage_switch(storage, SDMMC_SWITCH(MMC_SWITCH_MODE_WRITE_BYTE, EXT_CSD_PART_CONFIG, partition)))
|
|
return 0;
|
|
if (!_sdmmc_storage_check_status(storage))
|
|
return 0;
|
|
storage->partition = partition;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* SD specific functions.
|
|
*/
|
|
|
|
static int _sd_storage_execute_app_cmd(sdmmc_storage_t *storage, u32 expected_state, u32 mask, sdmmc_cmd_t *cmd, sdmmc_req_t *req, u32 *blkcnt_out)
|
|
{
|
|
u32 tmp;
|
|
if (!_sdmmc_storage_execute_cmd_type1_ex(storage, &tmp, MMC_APP_CMD, storage->rca << 16, 0, expected_state, mask))
|
|
return 0;
|
|
return sdmmc_execute_cmd(storage->sdmmc, cmd, req, blkcnt_out);
|
|
}
|
|
|
|
static int _sd_storage_execute_app_cmd_type1(sdmmc_storage_t *storage, u32 *resp, u32 cmd, u32 arg, u32 check_busy, u32 expected_state)
|
|
{
|
|
if (!_sdmmc_storage_execute_cmd_type1(storage, MMC_APP_CMD, storage->rca << 16, 0, R1_STATE_TRAN))
|
|
return 0;
|
|
return _sdmmc_storage_execute_cmd_type1_ex(storage, resp, cmd, arg, check_busy, expected_state, 0);
|
|
}
|
|
|
|
static int _sd_storage_send_if_cond(sdmmc_storage_t *storage)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, SD_SEND_IF_COND, 0x1AA, SDMMC_RSP_TYPE_5, 0);
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, 0, 0))
|
|
return 0;
|
|
|
|
//TODO: we may have received a timeout error in the above request, which indicates a version 1 card.
|
|
|
|
u32 resp = 0;
|
|
if (!sdmmc_get_rsp(storage->sdmmc, &resp, 4, SDMMC_RSP_TYPE_5))
|
|
return 0;
|
|
|
|
return (resp & 0xFFF) == 0x1AA ? 1 : 0;
|
|
}
|
|
|
|
static int _sd_storage_get_op_cond_once(sdmmc_storage_t *storage, u32 *cond, int is_version_1, int supports_low_voltage)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
u32 arg = (((~is_version_1 & 1) << 28) & 0xBFFFFFFF | ((~is_version_1 & 1) << 30)) & 0xFEFFFFFF | ((supports_low_voltage & ~is_version_1 & 1) << 24) | 0x100000;
|
|
sdmmc_init_cmd(&cmdbuf, SD_APP_OP_COND, arg, SDMMC_RSP_TYPE_3, 0);
|
|
if (!_sd_storage_execute_app_cmd(storage, 0x10, is_version_1 ? 0x400000 : 0, &cmdbuf, 0, 0))
|
|
return 0;
|
|
return sdmmc_get_rsp(storage->sdmmc, cond, 4, SDMMC_RSP_TYPE_3);
|
|
}
|
|
|
|
static int _sd_storage_get_op_cond(sdmmc_storage_t *storage, int is_version_1, int supports_low_voltage)
|
|
{
|
|
u32 timeout = get_tmr() + 1500000;
|
|
|
|
while (1)
|
|
{
|
|
u32 cond = 0;
|
|
if (!_sd_storage_get_op_cond_once(storage, &cond, is_version_1, supports_low_voltage))
|
|
break;
|
|
if (cond & 0x80000000)
|
|
{
|
|
if (cond & 0x40000000)
|
|
storage->has_sector_access = 1;
|
|
|
|
if (cond & 0x1000000 && supports_low_voltage)
|
|
{
|
|
//The low voltage regulator configuration is valid for SDMMC1 only.
|
|
if (storage->sdmmc->id == SDMMC_1 &&
|
|
_sdmmc_storage_execute_cmd_type1(storage, SD_SWITCH_VOLTAGE, 0, 0, R1_STATE_READY))
|
|
{
|
|
if (!sdmmc_enable_low_voltage(storage->sdmmc))
|
|
return 0;
|
|
storage->is_low_voltage = 1;
|
|
|
|
DPRINTF("-> switched to low voltage\n");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
if (get_tmr() > timeout)
|
|
break;
|
|
sleep(1000);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _sd_storage_get_rca(sdmmc_storage_t *storage)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, SD_SEND_RELATIVE_ADDR, 0, SDMMC_RSP_TYPE_4, 0);
|
|
|
|
u32 timeout = get_tmr() + 1500000;
|
|
|
|
while (1)
|
|
{
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, 0, 0))
|
|
break;
|
|
|
|
u32 resp = 0;
|
|
if (!sdmmc_get_rsp(storage->sdmmc, &resp, 4, SDMMC_RSP_TYPE_4))
|
|
break;
|
|
|
|
if (resp >> 16)
|
|
{
|
|
storage->rca = resp >> 16;
|
|
return 1;
|
|
}
|
|
|
|
if (get_tmr() > timeout)
|
|
break;
|
|
sleep(1000);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int _sd_storage_get_scr(sdmmc_storage_t *storage, void *buf)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, SD_APP_SEND_SCR, 0, SDMMC_RSP_TYPE_1, 0);
|
|
|
|
sdmmc_req_t reqbuf;
|
|
reqbuf.buf = buf;
|
|
reqbuf.blksize = 8;
|
|
reqbuf.num_sectors = 1;
|
|
reqbuf.is_write = 0;
|
|
reqbuf.is_multi_block = 0;
|
|
reqbuf.is_auto_cmd12 = 0;
|
|
|
|
if (!_sd_storage_execute_app_cmd(storage, R1_STATE_TRAN, 0, &cmdbuf, &reqbuf, 0))
|
|
return 0;
|
|
|
|
u32 tmp = 0;
|
|
sdmmc_get_rsp(storage->sdmmc, &tmp, 4, SDMMC_RSP_TYPE_1);
|
|
return _sdmmc_storage_check_result(tmp);
|
|
}
|
|
|
|
int _sd_storage_switch_get(sdmmc_storage_t *storage, void *buf)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, SD_SWITCH, 0xFFFFFF, SDMMC_RSP_TYPE_1, 0);
|
|
|
|
sdmmc_req_t reqbuf;
|
|
reqbuf.buf = buf;
|
|
reqbuf.blksize = 64;
|
|
reqbuf.num_sectors = 1;
|
|
reqbuf.is_write = 0;
|
|
reqbuf.is_multi_block = 0;
|
|
reqbuf.is_auto_cmd12 = 0;
|
|
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, 0))
|
|
return 0;
|
|
|
|
u32 tmp = 0;
|
|
sdmmc_get_rsp(storage->sdmmc, &tmp, 4, SDMMC_RSP_TYPE_1);
|
|
return _sdmmc_storage_check_result(tmp);
|
|
}
|
|
|
|
int _sd_storage_switch(sdmmc_storage_t *storage, void *buf, int flag, u32 arg)
|
|
{
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, SD_SWITCH, arg | (flag << 31) | 0xFFFFF0, SDMMC_RSP_TYPE_1, 0);
|
|
|
|
sdmmc_req_t reqbuf;
|
|
reqbuf.buf = buf;
|
|
reqbuf.blksize = 64;
|
|
reqbuf.num_sectors = 1;
|
|
reqbuf.is_write = 0;
|
|
reqbuf.is_multi_block = 0;
|
|
reqbuf.is_auto_cmd12 = 0;
|
|
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, 0))
|
|
return 0;
|
|
|
|
u32 tmp = 0;
|
|
sdmmc_get_rsp(storage->sdmmc, &tmp, 4, SDMMC_RSP_TYPE_1);
|
|
return _sdmmc_storage_check_result(tmp);
|
|
}
|
|
|
|
int _sd_storage_enable_highspeed(sdmmc_storage_t *storage, u32 hs_type, u8 *buf)
|
|
{
|
|
if (!_sd_storage_switch(storage, buf, 0, hs_type))
|
|
return 0;
|
|
|
|
u32 type_out = buf[16] & 0xF;
|
|
if (type_out != hs_type)
|
|
return 0;
|
|
|
|
if (((u16)buf[0] << 8) | buf[1] < 0x320)
|
|
{
|
|
if (!_sd_storage_switch(storage, buf, 1, hs_type))
|
|
return 0;
|
|
|
|
if (type_out != buf[16] & 0xF)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int _sd_storage_enable_highspeed_low_volt(sdmmc_storage_t *storage, u32 type, u8 *buf)
|
|
{
|
|
if (sdmmc_get_bus_width(storage->sdmmc) != SDMMC_BUS_WIDTH_4)
|
|
return 0;
|
|
|
|
if (!_sd_storage_switch_get(storage, buf))
|
|
return 0;
|
|
|
|
u32 hs_type = 0;
|
|
switch (type)
|
|
{
|
|
case 11:
|
|
if (buf[13] & 8)
|
|
{
|
|
type = 11;
|
|
hs_type = 3;
|
|
break;
|
|
}
|
|
//Fall through.
|
|
case 10:
|
|
if (!(buf[13] & 4))
|
|
return 0;
|
|
type = 10;
|
|
hs_type = 2;
|
|
break;
|
|
default:
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
if (!_sd_storage_enable_highspeed(storage, hs_type, buf))
|
|
return 0;
|
|
if (!sdmmc_setup_clock(storage->sdmmc, type))
|
|
return 0;
|
|
if (!sdmmc_config_tuning(storage->sdmmc, type, MMC_SEND_TUNING_BLOCK))
|
|
return 0;
|
|
return _sdmmc_storage_check_status(storage);
|
|
}
|
|
|
|
int _sd_storage_enable_highspeed_high_volt(sdmmc_storage_t *storage, u8 *buf)
|
|
{
|
|
if (!_sd_storage_switch_get(storage, buf))
|
|
return 0;
|
|
|
|
if (!(buf[13] & 2))
|
|
return 1;
|
|
|
|
if (!_sd_storage_enable_highspeed(storage, 1, buf))
|
|
return 0;
|
|
if (!_sdmmc_storage_check_status(storage))
|
|
return 0;
|
|
return sdmmc_setup_clock(storage->sdmmc, 7);
|
|
}
|
|
|
|
int sdmmc_storage_init_sd(sdmmc_storage_t *storage, sdmmc_t *sdmmc, u32 id, u32 bus_width, u32 type)
|
|
{
|
|
memset(storage, 0, sizeof(sdmmc_storage_t));
|
|
storage->sdmmc = sdmmc;
|
|
|
|
if (!sdmmc_init(sdmmc, id, SDMMC_POWER_3_3, SDMMC_BUS_WIDTH_1, 5, 0))
|
|
return 0;
|
|
DPRINTF("[sd] after init\n");
|
|
|
|
sleep(1000 + (74000 + sdmmc->divisor - 1) / sdmmc->divisor);
|
|
|
|
if (!_sdmmc_storage_go_idle_state(storage))
|
|
return 0;
|
|
DPRINTF("[sd] went to idle state\n");
|
|
|
|
if (!_sd_storage_send_if_cond(storage))
|
|
return 0;
|
|
DPRINTF("[sd] after send if cond\n");
|
|
|
|
//TODO: use correct version here -----v
|
|
if (!_sd_storage_get_op_cond(storage, 0, bus_width == SDMMC_BUS_WIDTH_4 && (type | 1) == 11))
|
|
return 0;
|
|
DPRINTF("[sd] got op cond\n");
|
|
|
|
if (!_sdmmc_storage_get_cid(storage, storage->cid))
|
|
return 0;
|
|
DPRINTF("[sd] got cid\n");
|
|
|
|
if (!_sd_storage_get_rca(storage))
|
|
return 0;
|
|
DPRINTF("[sd] got rca (= %04X)\n", storage->rca);
|
|
|
|
if (!_sdmmc_storage_get_csd(storage, storage->csd))
|
|
return 0;
|
|
DPRINTF("[sd] got csd\n");
|
|
|
|
//Parse CSD.
|
|
u32 *csd = (u32 *)storage->csd;
|
|
u32 csd_struct = unstuff_bits(csd, 126, 2);
|
|
switch (csd_struct)
|
|
{
|
|
case 0:
|
|
storage->sec_cnt = (1 + unstuff_bits(csd, 62, 12)) << (unstuff_bits(csd, 47, 3) + 2);
|
|
break;
|
|
case 1:
|
|
storage->sec_cnt = (1 + unstuff_bits(csd, 48, 22)) << 10;
|
|
break;
|
|
default:
|
|
DPRINTF("[sd] Unknown CSD structure %d\n", csd_struct);
|
|
//TODO: I've encountered this with one of my SD cards, but
|
|
// according to the spec only version 0 and 1 are
|
|
// supposed to be in use (mine was version 2).
|
|
//return 0;
|
|
break;
|
|
}
|
|
|
|
if (!storage->is_low_voltage)
|
|
{
|
|
if (!sdmmc_setup_clock(storage->sdmmc, 6))
|
|
return 0;
|
|
DPRINTF("[sd] after setup clock\n");
|
|
}
|
|
|
|
if (!_sdmmc_storage_select_card(storage))
|
|
return 0;
|
|
DPRINTF("[sd] card selected\n");
|
|
|
|
if (!_sdmmc_storage_set_blocklen(storage, 512))
|
|
return 0;
|
|
DPRINTF("[sd] set blocklen to 512\n");
|
|
|
|
u32 tmp = 0;
|
|
if (!_sd_storage_execute_app_cmd_type1(storage, &tmp, SD_APP_SET_CLR_CARD_DETECT, 0, 0, R1_STATE_TRAN))
|
|
return 0;
|
|
DPRINTF("[sd] cleared card detect\n");
|
|
|
|
u8 *buf = (u8 *)malloc(512);
|
|
if (!_sd_storage_get_scr(storage, buf))
|
|
return 0;
|
|
memcpy(storage->scr, buf, 8);
|
|
DPRINTF("[sd] got scr\n");
|
|
|
|
if (bus_width == SDMMC_BUS_WIDTH_4 && storage->scr[1] & 4)
|
|
{
|
|
if (!_sd_storage_execute_app_cmd_type1(storage, &tmp, SD_APP_SET_BUS_WIDTH, SD_BUS_WIDTH_4, 0, R1_STATE_TRAN))
|
|
{
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
sdmmc_set_bus_width(storage->sdmmc, SDMMC_BUS_WIDTH_4);
|
|
DPRINTF("[sd] switched to wide bus width\n");
|
|
}
|
|
else
|
|
DPRINTF("[sd] SD does not support wide bus width\n");
|
|
|
|
if (storage->is_low_voltage)
|
|
{
|
|
if (!_sd_storage_enable_highspeed_low_volt(storage, type, buf))
|
|
{
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
DPRINTF("[sd] enabled highspeed (low voltage)\n");
|
|
}
|
|
else if (type != 6 && storage->scr[0] & 0xF != 0)
|
|
{
|
|
if (!_sd_storage_enable_highspeed_high_volt(storage, buf))
|
|
{
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
DPRINTF("[sd] enabled highspeed (high voltage)\n");
|
|
}
|
|
|
|
sdmmc_sd_clock_ctrl(sdmmc, 1);
|
|
|
|
free(buf);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Gamecard specific functions.
|
|
*/
|
|
|
|
int _gc_storage_custom_cmd(sdmmc_storage_t *storage, void *buf)
|
|
{
|
|
u32 resp;
|
|
sdmmc_cmd_t cmdbuf;
|
|
sdmmc_init_cmd(&cmdbuf, 60, 0, SDMMC_RSP_TYPE_1, 1);
|
|
|
|
sdmmc_req_t reqbuf;
|
|
reqbuf.buf = buf;
|
|
reqbuf.blksize = 0x40;
|
|
reqbuf.num_sectors = 1;
|
|
reqbuf.is_write = 1;
|
|
reqbuf.is_multi_block = 0;
|
|
reqbuf.is_auto_cmd12 = 0;
|
|
|
|
if (!sdmmc_execute_cmd(storage->sdmmc, &cmdbuf, &reqbuf, 0))
|
|
{
|
|
sdmmc_stop_transmission(storage->sdmmc, &resp);
|
|
return 0;
|
|
}
|
|
|
|
if (!sdmmc_get_rsp(storage->sdmmc, &resp, 4, SDMMC_RSP_TYPE_1))
|
|
return 0;
|
|
if (!_sdmmc_storage_check_result(resp))
|
|
return 0;
|
|
return _sdmmc_storage_check_status(storage);
|
|
}
|
|
|
|
int sdmmc_storage_init_gc(sdmmc_storage_t *storage, sdmmc_t *sdmmc)
|
|
{
|
|
memset(storage, 0, sizeof(sdmmc_storage_t));
|
|
storage->sdmmc = sdmmc;
|
|
|
|
if (!sdmmc_init(sdmmc, SDMMC_2, SDMMC_POWER_1_8, SDMMC_BUS_WIDTH_8, 14, 0))
|
|
return 0;
|
|
DPRINTF("[gc] after init\n");
|
|
|
|
sleep(1000 + (10000 + sdmmc->divisor - 1) / sdmmc->divisor);
|
|
|
|
if (!sdmmc_config_tuning(storage->sdmmc, 14, MMC_SEND_TUNING_BLOCK_HS200))
|
|
return 0;
|
|
DPRINTF("[gc] after tuning\n");
|
|
|
|
sdmmc_sd_clock_ctrl(sdmmc, 1);
|
|
|
|
return 1;
|
|
}
|