You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1207 lines
35 KiB
1207 lines
35 KiB
/* vim: set ts=4 sw=4 fenc=cp936 et fdm=marker: */
|
|
/*
|
|
* mmc driver
|
|
*
|
|
* ChangeLog:
|
|
* 2022/03/29: surenyi926 add
|
|
*/
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include "mmcDevice.h"
|
|
#include "mmcPlatform.h"
|
|
#include "mmc.h" /* MMC_BLKSIZE */
|
|
/* {{{: mmc devices */
|
|
static struct mmc_device *__mmc_dev_list = NULL, **__mmc_dev_tail = &__mmc_dev_list;
|
|
/* }}} */
|
|
|
|
/* {{{: driver api */
|
|
int mmcAddDevice(struct mmc_device *dev)
|
|
{
|
|
struct mmc_device *mmc;
|
|
|
|
if ((dev == NULL) || (mmcLock() != 0)) {
|
|
return -1;
|
|
}
|
|
|
|
/* redundancy ? */
|
|
mmc = __mmc_dev_list;
|
|
while (mmc) {
|
|
if (strcmp(mmc_device_name(mmc), mmc_device_name(dev)) == 0) {
|
|
mmcPrintf("%s is in the mmc subsystem\r\n", mmc_device_name(dev));
|
|
mmcUnlock();
|
|
return 1;
|
|
}
|
|
mmc = mmc->next;
|
|
}
|
|
|
|
dev->next = NULL;
|
|
*__mmc_dev_tail = dev;
|
|
__mmc_dev_tail = &dev->next;
|
|
mmcUnlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mmcSetDSR(struct mmc_device *dev, uint16_t dsr)
|
|
{
|
|
dev->dsr = dsr;
|
|
}
|
|
|
|
int mmcSetCapacity(struct mmc_device *mmc, int part_num)
|
|
{
|
|
if ((mmc->quirks & MMC_QUIRK_CAPACITY) == MMC_QUIRK_CAPACITY) {
|
|
return 0;
|
|
}
|
|
|
|
switch (part_num) {
|
|
case 0:
|
|
mmc->capacity = mmc->capacity_user;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
mmc->capacity = mmc->capacity_boot;
|
|
break;
|
|
case 3:
|
|
mmc->capacity = mmc->capacity_rpmb;
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
mmc->capacity = mmc->capacity_gp[part_num - 4];
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int mmcSendStatus(struct mmc_device *mmc, int timeout)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
int err, retries = 5;
|
|
|
|
/* CMD13: SEND_STATUS */
|
|
cmd.cmdidx = 13;
|
|
cmd.resp_type = MMC_RSP_R1;
|
|
cmd.cmdarg = mmc->rca << 16;
|
|
while (1) {
|
|
err = mmc_send_command(mmc, &cmd, NULL);
|
|
if (!err) {
|
|
if ((cmd.response[0] & MMC_STATUS_RDY_FOR_DATA) && ((cmd.response[0] & MMC_STATUS_CURR_STATE) != MMC_STATE_PRG))
|
|
break;
|
|
|
|
if (cmd.response[0] & MMC_STATUS_MASK) {
|
|
mmcPrintf("Status Error: 0x%08x\r\n", cmd.response[0]);
|
|
return -70;
|
|
}
|
|
} else if (--retries < 0)
|
|
return err;
|
|
|
|
if (timeout-- <= 0)
|
|
break;
|
|
|
|
mmcDelayUs(1000);
|
|
}
|
|
|
|
if (timeout <= 0) {
|
|
mmcPrintf("Timeout waiting card ready\r\n");
|
|
return -110;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const uint8_t tuning_blk_pattern_4bit[] = {
|
|
0xff, 0x0f, 0xff, 0x00, 0xff, 0xcc, 0xc3, 0xcc,
|
|
0xc3, 0x3c, 0xcc, 0xff, 0xfe, 0xff, 0xfe, 0xef,
|
|
0xff, 0xdf, 0xff, 0xdd, 0xff, 0xfb, 0xff, 0xfb,
|
|
0xbf, 0xff, 0x7f, 0xff, 0x77, 0xf7, 0xbd, 0xef,
|
|
0xff, 0xf0, 0xff, 0xf0, 0x0f, 0xfc, 0xcc, 0x3c,
|
|
0xcc, 0x33, 0xcc, 0xcf, 0xff, 0xef, 0xff, 0xee,
|
|
0xff, 0xfd, 0xff, 0xfd, 0xdf, 0xff, 0xbf, 0xff,
|
|
0xbb, 0xff, 0xf7, 0xff, 0xf7, 0x7f, 0x7b, 0xde,
|
|
};
|
|
|
|
static const uint8_t tuning_blk_pattern_8bit[] = {
|
|
0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
|
|
0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc,
|
|
0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff,
|
|
0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff,
|
|
0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd,
|
|
0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb,
|
|
0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff,
|
|
0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff,
|
|
0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00,
|
|
0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc,
|
|
0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff,
|
|
0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee,
|
|
0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd,
|
|
0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff,
|
|
0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff,
|
|
0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee,
|
|
};
|
|
|
|
int mmcSendTuning(struct mmc_device *mmc, unsigned int opcode)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
struct mmc_data data;
|
|
const uint8_t *tuning_block_pattern;
|
|
uint8_t data_buf[128];
|
|
int size, err;
|
|
|
|
if (mmc->bus_width == 8) {
|
|
tuning_block_pattern = tuning_blk_pattern_8bit;
|
|
size = sizeof(tuning_blk_pattern_8bit);
|
|
} else if (mmc->bus_width == 4) {
|
|
tuning_block_pattern = tuning_blk_pattern_4bit;
|
|
size = sizeof(tuning_blk_pattern_4bit);
|
|
} else {
|
|
return -22;
|
|
}
|
|
|
|
/* CMD21: SEND_TUNNING_BLOCK */
|
|
cmd.cmdidx = opcode;
|
|
cmd.cmdarg = 0;
|
|
cmd.resp_type = MMC_RSP_R1;
|
|
|
|
data.dest = (void *)data_buf;
|
|
data.blocks = 1;
|
|
data.blocksize = size;
|
|
data.flags = MMC_DATA_READ;
|
|
|
|
err = mmc_send_command(mmc, &cmd, &data);
|
|
if (err)
|
|
return err;
|
|
|
|
if (memcmp(data_buf, tuning_block_pattern, size)) {
|
|
return -5;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mmcForEachDevice(int (*func)(struct mmc_device *, void *arg), void *arg)
|
|
{
|
|
struct mmc_device *cur, *next;
|
|
|
|
if (!func) {
|
|
return;
|
|
}
|
|
|
|
if (mmcLock() != 0) {
|
|
return;
|
|
}
|
|
cur = __mmc_dev_list;
|
|
while (cur) {
|
|
next = cur->next;
|
|
if (func(cur, arg)) {
|
|
break;
|
|
}
|
|
cur = next;
|
|
}
|
|
mmcUnlock();
|
|
}
|
|
|
|
void mmcPrintInfo(struct mmc_device *mmc)
|
|
{
|
|
int i;
|
|
double cap;
|
|
|
|
mmcPrintf(" Device: %s\r\n", mmc->name);
|
|
mmcPrintf(" Manufacturer ID: %x\r\n", mmc->cid[0] >> 24);
|
|
mmcPrintf(" OEM: %x\r\n", (mmc->cid[0] >> 8) & 0xffff);
|
|
mmcPrintf(" Name: %c%c%c%c%c[%02x-%02x-%02x-%02x-%02x]\r\n",
|
|
mmc->cid[0] & 0xff, (mmc->cid[1] >> 24), (mmc->cid[1] >> 16) & 0xff,
|
|
(mmc->cid[1] >> 8) & 0xff, mmc->cid[1] & 0xff,
|
|
mmc->cid[0] & 0xff, (mmc->cid[1] >> 24), (mmc->cid[1] >> 16) & 0xff,
|
|
(mmc->cid[1] >> 8) & 0xff, mmc->cid[1] & 0xff);
|
|
|
|
mmcPrintf(" Rd Block Len: %d\r\n", mmc->read_bl_len);
|
|
mmcPrintf(" MMC version: %d.%d",
|
|
EXTRACT_SDMMC_MAJOR_VERSION(mmc->version),
|
|
EXTRACT_SDMMC_MINOR_VERSION(mmc->version));
|
|
if (EXTRACT_SDMMC_CHANGE_VERSION(mmc->version) != 0)
|
|
mmcPrintf(".%d", EXTRACT_SDMMC_CHANGE_VERSION(mmc->version));
|
|
mmcPrintf("\r\n");
|
|
|
|
cap = 1.0 * mmc->capacity / 1024.0 / 1024.0 / 1024.0;
|
|
mmcPrintf(" High Capacity: %s\r\n", ((mmc->ocr & OCR_HCS) == OCR_HCS) ? "Yes" : "No");
|
|
mmcPrintf(" Capacity: %.2lfGB\r\n", cap);
|
|
|
|
mmcPrintf(" Bus Width: %d-bit%s\r\n", mmc->bus_width, mmc->ddr_mode ? " DDR" : "");
|
|
mmcPrintf(" Erase Group Size: %llu\r\n", (((uint64_t)mmc->erase_grp_size) << 9));
|
|
|
|
if (mmc->version >= MMC_VERSION_4_41) {
|
|
int has_enh = (mmc->part_support & ENHNCD_SUPPORT) != 0;
|
|
int usr_enh = has_enh && (mmc->part_attr & EXT_CSD_ENH_USR);
|
|
|
|
mmcPrintf(" User Capacity: %llu %s ", mmc->capacity_user, usr_enh ? " ENH" : "");
|
|
if (mmc->wr_rel_set & EXT_CSD_WR_DATA_REL_USR)
|
|
mmcPrintf(" WRREL\r\n");
|
|
else
|
|
mmcPrintf("\r\n");
|
|
if (usr_enh) {
|
|
mmcPrintf("User Enhanced Start: %llu\r\n", mmc->enh_user_start);
|
|
mmcPrintf(" User Enhanced Size: %llu\r\n", mmc->enh_user_size);
|
|
}
|
|
mmcPrintf("Boot Capacity: %llu %s\r\n", mmc->capacity_boot, has_enh ? " ENH" : "");
|
|
mmcPrintf("RPMB Capacity: %llu %s\r\n", mmc->capacity_rpmb, has_enh ? " ENH" : "");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mmc->capacity_gp); i++) {
|
|
int is_enh = has_enh && (mmc->part_attr & EXT_CSD_ENH_GP(i));
|
|
if (mmc->capacity_gp[i]) {
|
|
mmcPrintf("GP%i Capacity: %llu%s", i+1, mmc->capacity_gp[i], is_enh ? " ENH" : "");
|
|
if (mmc->wr_rel_set & EXT_CSD_WR_DATA_REL_GP(i))
|
|
mmcPrintf(" WRREL\r\n");
|
|
else
|
|
mmcPrintf("\r\n");
|
|
}
|
|
}
|
|
}
|
|
mmcPrintf("\r\n");
|
|
mmcPrintf("CID: %08x-%08x-%08x-%08x\r\n", mmc->cid[0], mmc->cid[1], mmc->cid[2], mmc->cid[3]);
|
|
}
|
|
|
|
static inline int goto_idle(struct mmc_device *mmc)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
|
|
/* CMD0: put mmc device to idle mode*/
|
|
cmd.cmdidx = 0; /* CMD0 */
|
|
cmd.cmdarg = 0;
|
|
cmd.resp_type = MMC_RSP_NONE;
|
|
return mmc_send_command(mmc, &cmd, NULL);
|
|
}
|
|
|
|
int mmcGoIdle(struct mmc_device *mmc)
|
|
{
|
|
return goto_idle(mmc);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{: interal functions */
|
|
|
|
static int send_op_cond(struct mmc_device *mmc)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
int err;
|
|
|
|
/* CMD1: set operations conditions */
|
|
cmd.cmdidx = 1;
|
|
cmd.resp_type = MMC_RSP_R3;
|
|
cmd.cmdarg = mmc->voltages & OCR_VOLTAGE_MASK;
|
|
if (mmc->hcs_mode) {
|
|
cmd.cmdarg |= OCR_HCS;
|
|
}
|
|
err = mmc_send_command(mmc, &cmd, NULL);
|
|
if (err)
|
|
return err;
|
|
mmc->ocr = cmd.response[0];
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_send_ext_csd(struct mmc_device *mmc, uint8_t *ext_csd)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
struct mmc_data data;
|
|
|
|
/* CMD8: Get the Card Status Register */
|
|
cmd.cmdidx = 8;
|
|
cmd.resp_type = MMC_RSP_R1;
|
|
cmd.cmdarg = 0;
|
|
|
|
data.dest = (char *)ext_csd;
|
|
data.blocks = 1;
|
|
data.blocksize = MMC_BLKSIZE;
|
|
data.flags = MMC_DATA_READ;
|
|
|
|
return mmc_send_command(mmc, &cmd, &data);
|
|
}
|
|
|
|
static int __mmc_switch(struct mmc_device *mmc, uint8_t index, uint8_t value, int send_status)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
int timeout = 1000;
|
|
int retries = 3;
|
|
int ret;
|
|
|
|
/* CMD6: switch */
|
|
cmd.cmdidx = 6;
|
|
cmd.resp_type = MMC_RSP_R1b;
|
|
cmd.cmdarg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (index << 16) | (value << 8);
|
|
|
|
/* mmcPrintf("switch (6): %x\r\n", cmd.cmdarg); */
|
|
|
|
while (retries > 0) {
|
|
ret = mmc_send_command(mmc, &cmd, NULL);
|
|
|
|
if (ret) {
|
|
retries--;
|
|
continue;
|
|
}
|
|
|
|
if (!send_status) {
|
|
mmcDelayUs(50000);
|
|
return 0;
|
|
}
|
|
|
|
/* Waiting for the ready status */
|
|
return mmcSendStatus(mmc, timeout);
|
|
}
|
|
/* mmcPrintf("%s[%d]: %d\r\n", __func__, __LINE__, ret); */
|
|
return ret;
|
|
}
|
|
|
|
static int mmc_switch(struct mmc_device *mmc, uint8_t index, uint8_t value)
|
|
{
|
|
return __mmc_switch(mmc, index, value, 1);
|
|
}
|
|
|
|
static int mmc_startup_v4(struct mmc_device *mmc)
|
|
{
|
|
int err, i;
|
|
uint64_t capacity;
|
|
int has_parts = 0;
|
|
int part_completed;
|
|
uint8_t *ext_csd;
|
|
static const uint32_t mmc_versions[] = {
|
|
MMC_VERSION_4,
|
|
MMC_VERSION_4_1,
|
|
MMC_VERSION_4_2,
|
|
MMC_VERSION_4_3,
|
|
MMC_VERSION_4_4,
|
|
MMC_VERSION_4_41,
|
|
MMC_VERSION_4_5,
|
|
MMC_VERSION_5_0,
|
|
MMC_VERSION_5_1
|
|
};
|
|
|
|
if (mmc->version < MMC_VERSION_4)
|
|
return 0;
|
|
|
|
/* check ext_csd version and capacity */
|
|
err = mmc_send_ext_csd(mmc, mmc->ext_csd);
|
|
if (err)
|
|
goto error;
|
|
|
|
ext_csd = mmc->ext_csd;
|
|
#if 0
|
|
for (i = 0; i < sizeof mmc->ext_csd; ++i) {
|
|
mmcPrintf("%02x%s", mmc->ext_csd[i], (i + 1) % 16 == 0 ? "\r\n" : " ");
|
|
}
|
|
mmcPrintf("\r\n");
|
|
#endif
|
|
if (ext_csd[EXT_CSD_REV] >= ARRAY_SIZE(mmc_versions)) {
|
|
/* mmcPrintf("EXT_CSD_REV: %d\r\n", ext_csd[EXT_CSD_REV]); */
|
|
return -22;
|
|
}
|
|
|
|
mmc->version = mmc_versions[ext_csd[EXT_CSD_REV]];
|
|
|
|
if (mmc->version >= MMC_VERSION_4_2) {
|
|
/*
|
|
* According to the JEDEC Standard, the value of
|
|
* ext_csd's capacity is valid if the value is more
|
|
* than 2GB
|
|
*/
|
|
capacity = ext_csd[EXT_CSD_SEC_CNT] << 0
|
|
| ext_csd[EXT_CSD_SEC_CNT + 1] << 8
|
|
| ext_csd[EXT_CSD_SEC_CNT + 2] << 16
|
|
| ext_csd[EXT_CSD_SEC_CNT + 3] << 24;
|
|
capacity *= MMC_BLKSIZE;
|
|
if ((capacity >> 20) > 2 * 1024)
|
|
mmc->capacity_user = capacity;
|
|
}
|
|
|
|
/* The partition data may be non-zero but it is only
|
|
* effective if PARTITION_SETTING_COMPLETED is set in
|
|
* EXT_CSD, so ignore any data if this bit is not set,
|
|
* except for enabling the high-capacity group size
|
|
* definition (see below).
|
|
*/
|
|
part_completed = !!(ext_csd[EXT_CSD_PARTITION_SETTING] &
|
|
EXT_CSD_PARTITION_SETTING_COMPLETED);
|
|
|
|
/* store the partition info of emmc */
|
|
mmc->part_support = ext_csd[EXT_CSD_PARTITIONING_SUPPORT];
|
|
if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) ||
|
|
ext_csd[EXT_CSD_BOOT_MULT])
|
|
mmc->part_config = ext_csd[EXT_CSD_PART_CONF];
|
|
if (part_completed &&
|
|
(ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & ENHNCD_SUPPORT))
|
|
mmc->part_attr = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE];
|
|
|
|
mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17;
|
|
|
|
mmc->capacity_rpmb = ext_csd[EXT_CSD_RPMB_MULT] << 17;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
int idx = EXT_CSD_GP_SIZE_MULT + i * 3;
|
|
uint32_t mult = (ext_csd[idx + 2] << 16) + (ext_csd[idx + 1] << 8) + ext_csd[idx];
|
|
if (mult)
|
|
has_parts = 1;
|
|
if (!part_completed)
|
|
continue;
|
|
mmc->capacity_gp[i] = mult;
|
|
mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
|
|
mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
|
|
mmc->capacity_gp[i] <<= 19;
|
|
}
|
|
|
|
if (part_completed) {
|
|
mmc->enh_user_size =
|
|
(ext_csd[EXT_CSD_ENH_SIZE_MULT + 2] << 16) +
|
|
(ext_csd[EXT_CSD_ENH_SIZE_MULT + 1] << 8) +
|
|
ext_csd[EXT_CSD_ENH_SIZE_MULT];
|
|
mmc->enh_user_size *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
|
|
mmc->enh_user_size *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
|
|
mmc->enh_user_size <<= 19;
|
|
mmc->enh_user_start =
|
|
(ext_csd[EXT_CSD_ENH_START_ADDR + 3] << 24) +
|
|
(ext_csd[EXT_CSD_ENH_START_ADDR + 2] << 16) +
|
|
(ext_csd[EXT_CSD_ENH_START_ADDR + 1] << 8) +
|
|
ext_csd[EXT_CSD_ENH_START_ADDR];
|
|
if ((mmc->ocr & OCR_HCS) == OCR_HCS)
|
|
mmc->enh_user_start <<= 9;
|
|
}
|
|
|
|
/*
|
|
* Host needs to enable ERASE_GRP_DEF bit if device is
|
|
* partitioned. This bit will be lost every time after a reset
|
|
* or power off. This will affect erase size.
|
|
*/
|
|
if (part_completed)
|
|
has_parts = 1;
|
|
if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) &&
|
|
(ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & PART_ENH_ATTRIB))
|
|
has_parts = 1;
|
|
if (has_parts) {
|
|
err = mmc_switch(mmc, EXT_CSD_ERASE_GROUP_DEF, 1);
|
|
if (err)
|
|
goto error;
|
|
|
|
ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1;
|
|
}
|
|
|
|
if (ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x01) {
|
|
/* Read out group size from ext_csd */
|
|
mmc->erase_grp_size = ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024;
|
|
/*
|
|
* if high capacity and partition setting completed
|
|
* SEC_COUNT is valid even if it is smaller than 2 GiB
|
|
* JEDEC Standard JESD84-B45, 6.2.4
|
|
*/
|
|
if (((mmc->ocr & OCR_HCS) == OCR_HCS) && part_completed) {
|
|
capacity = (ext_csd[EXT_CSD_SEC_CNT]) | (ext_csd[EXT_CSD_SEC_CNT + 1] << 8)
|
|
| (ext_csd[EXT_CSD_SEC_CNT + 2] << 16) | (ext_csd[EXT_CSD_SEC_CNT + 3] << 24);
|
|
capacity *= MMC_BLKSIZE;
|
|
mmc->capacity_user = capacity;
|
|
}
|
|
} else {
|
|
/* Calculate the group size from the csd value. */
|
|
int erase_gsz, erase_gmul;
|
|
erase_gsz = (mmc->csd[2] & 0x00007c00) >> 10;
|
|
erase_gmul = (mmc->csd[2] & 0x000003e0) >> 5;
|
|
mmc->erase_grp_size = (erase_gsz + 1) * (erase_gmul + 1);
|
|
}
|
|
|
|
mmc->wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET];
|
|
return 0;
|
|
|
|
error:
|
|
return err;
|
|
}
|
|
|
|
const char *mmc_mode_name(enum bus_mode mode)
|
|
{
|
|
static const char *const names[] = {
|
|
[MMC_LEGACY] = "MMC legacy",
|
|
[SD_LEGACY] = "SD Legacy",
|
|
[MMC_HS] = "MMC High Speed (26MHz)",
|
|
[SD_HS] = "SD High Speed (50MHz)",
|
|
[UHS_SDR12] = "UHS SDR12 (25MHz)",
|
|
[UHS_SDR25] = "UHS SDR25 (50MHz)",
|
|
[UHS_SDR50] = "UHS SDR50 (100MHz)",
|
|
[UHS_SDR104] = "UHS SDR104 (208MHz)",
|
|
[UHS_DDR50] = "UHS DDR50 (50MHz)",
|
|
[MMC_HS_52] = "MMC High Speed (52MHz)",
|
|
[MMC_DDR_52] = "MMC DDR52 (52MHz)",
|
|
[MMC_HS_200] = "HS200 (200MHz)",
|
|
[MMC_HS_400] = "HS400 (200MHz)",
|
|
};
|
|
|
|
if (mode >= MMC_MODES_END)
|
|
return "Unknown mode";
|
|
else
|
|
return names[mode];
|
|
}
|
|
|
|
static uint32_t mmc_mode2freq(struct mmc_device *mmc, enum bus_mode mode)
|
|
{
|
|
static const int freqs[] = {
|
|
[MMC_LEGACY] = 25000000,
|
|
[SD_LEGACY] = 25000000,
|
|
[MMC_HS] = 26000000,
|
|
[SD_HS] = 50000000,
|
|
[MMC_HS_52] = 52000000,
|
|
[MMC_DDR_52] = 52000000,
|
|
[UHS_SDR12] = 25000000,
|
|
[UHS_SDR25] = 50000000,
|
|
[UHS_SDR50] = 100000000,
|
|
[UHS_DDR50] = 50000000,
|
|
[UHS_SDR104] = 208000000,
|
|
[MMC_HS_200] = 200000000,
|
|
[MMC_HS_400] = 200000000,
|
|
};
|
|
|
|
if (mode == MMC_LEGACY)
|
|
return mmc->legacy_speed;
|
|
|
|
if (mode >= MMC_MODES_END)
|
|
return 0;
|
|
else
|
|
return freqs[mode];
|
|
}
|
|
|
|
static inline int mmc_is_mode_ddr(enum bus_mode mode)
|
|
{
|
|
if (mode == MMC_DDR_52)
|
|
return 1;
|
|
else if (mode == UHS_DDR50)
|
|
return 1;
|
|
else if (mode == MMC_HS_400)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_select_mode(struct mmc_device *mmc, enum bus_mode mode)
|
|
{
|
|
mmc->selected_mode = mode;
|
|
mmc->tran_speed = mmc_mode2freq(mmc, mode);
|
|
mmc->ddr_mode = mmc_is_mode_ddr(mode);
|
|
mmcPrintf("%s: selecting mode %s\r\n", mmc_device_name(mmc), mmc_mode_name(mode)); /* , mmc->tran_speed / 1000000); */
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_set_card_speed(struct mmc_device *mmc, enum bus_mode mode, int hsdowngrade)
|
|
{
|
|
int err;
|
|
int speed_bits;
|
|
uint8_t test_csd[MMC_BLKSIZE];
|
|
|
|
switch (mode) {
|
|
case MMC_HS:
|
|
case MMC_HS_52:
|
|
case MMC_DDR_52:
|
|
speed_bits = EXT_CSD_TIMING_HS;
|
|
break;
|
|
case MMC_HS_200:
|
|
speed_bits = EXT_CSD_TIMING_HS200;
|
|
break;
|
|
case MMC_HS_400:
|
|
speed_bits = EXT_CSD_TIMING_HS400;
|
|
break;
|
|
case MMC_LEGACY:
|
|
speed_bits = EXT_CSD_TIMING_LEGACY;
|
|
break;
|
|
default:
|
|
return -22;
|
|
}
|
|
|
|
err = __mmc_switch(mmc, EXT_CSD_HS_TIMING, speed_bits, !hsdowngrade);
|
|
if (err)
|
|
return err;
|
|
|
|
/*
|
|
* In case the eMMC is in HS200/HS400 mode and we are downgrading
|
|
* to HS mode, the card clock are still running much faster than
|
|
* the supported HS mode clock, so we can not reliably read out
|
|
* Extended CSD. Reconfigure the controller to run at HS mode.
|
|
*/
|
|
if (hsdowngrade) {
|
|
mmc_select_mode(mmc, MMC_HS);
|
|
mmc_set_clock(mmc, mmc_mode2freq(mmc, MMC_HS), 1);
|
|
}
|
|
|
|
if ((mode == MMC_HS) || (mode == MMC_HS_52)) {
|
|
/* Now check to see that it worked */
|
|
err = mmc_send_ext_csd(mmc, test_csd);
|
|
if (err)
|
|
return err;
|
|
|
|
/* No high-speed support */
|
|
if (!test_csd[EXT_CSD_HS_TIMING])
|
|
return -524;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_select_hs400(struct mmc_device *mmc)
|
|
{
|
|
int err;
|
|
|
|
/* Set timing to HS200 for tuning */
|
|
err = mmc_set_card_speed(mmc, MMC_HS_200, 0);
|
|
if (err)
|
|
return err;
|
|
|
|
/* configure the bus mode (host) */
|
|
mmc_select_mode(mmc, MMC_HS_200);
|
|
mmc_set_clock(mmc, mmc->tran_speed, 1);
|
|
|
|
/* execute tuning if needed */
|
|
err = mmc_execute_tuning(mmc, 21); /* CMD21 */
|
|
if (err) {
|
|
mmcPrintf("tuning failed\r\n");
|
|
return err;
|
|
}
|
|
|
|
/* Set back to HS */
|
|
mmc_set_card_speed(mmc, MMC_HS, 1);
|
|
|
|
err = mmc_switch(mmc, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_8 | EXT_CSD_DDR_FLAG);
|
|
if (err)
|
|
return err;
|
|
|
|
err = mmc_set_card_speed(mmc, MMC_HS_400, 0);
|
|
if (err)
|
|
return err;
|
|
|
|
mmc_select_mode(mmc, MMC_HS_400);
|
|
err = mmc_set_clock(mmc, mmc->tran_speed, 1);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int bus_width(uint32_t cap)
|
|
{
|
|
if (cap == MMC_MODE_8BIT)
|
|
return 8;
|
|
if (cap == MMC_MODE_4BIT)
|
|
return 4;
|
|
if (cap == MMC_MODE_1BIT)
|
|
return 1;
|
|
mmcPrintf("invalid bus witdh capability 0x%x\r\n", cap);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* read the compare the part of ext csd that is constant.
|
|
* This can be used to check that the transfer is working
|
|
* as expected.
|
|
*/
|
|
static int mmc_read_and_compare_ext_csd(struct mmc_device *mmc)
|
|
{
|
|
int err;
|
|
const uint8_t *ext_csd = mmc->ext_csd;
|
|
uint8_t test_csd[MMC_BLKSIZE];
|
|
|
|
if (mmc->version < MMC_VERSION_4)
|
|
return 0;
|
|
|
|
err = mmc_send_ext_csd(mmc, test_csd);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Only compare read only fields */
|
|
if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] == test_csd[EXT_CSD_PARTITIONING_SUPPORT] &&
|
|
ext_csd[EXT_CSD_HC_WP_GRP_SIZE] == test_csd[EXT_CSD_HC_WP_GRP_SIZE] &&
|
|
ext_csd[EXT_CSD_REV] == test_csd[EXT_CSD_REV] &&
|
|
ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] == test_csd[EXT_CSD_HC_ERASE_GRP_SIZE] &&
|
|
memcmp(&ext_csd[EXT_CSD_SEC_CNT], &test_csd[EXT_CSD_SEC_CNT], 4) == 0)
|
|
return 0;
|
|
|
|
return -74;
|
|
}
|
|
|
|
struct mode_width_tuning {
|
|
enum bus_mode mode;
|
|
uint32_t widths;
|
|
uint32_t tuning;
|
|
};
|
|
|
|
static const struct mode_width_tuning mmc_modes_by_pref[] = {
|
|
{
|
|
.mode = MMC_HS_400,
|
|
.widths = MMC_MODE_8BIT,
|
|
.tuning = 21, /* CMD21 */
|
|
},
|
|
{
|
|
.mode = MMC_HS_200,
|
|
.widths = MMC_MODE_8BIT | MMC_MODE_4BIT,
|
|
.tuning = 21, /* CMD21 */
|
|
},
|
|
{
|
|
.mode = MMC_DDR_52,
|
|
.widths = MMC_MODE_8BIT | MMC_MODE_4BIT,
|
|
},
|
|
{
|
|
.mode = MMC_HS_52,
|
|
.widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT,
|
|
},
|
|
{
|
|
.mode = MMC_HS,
|
|
.widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT,
|
|
},
|
|
{
|
|
.mode = MMC_LEGACY,
|
|
.widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT,
|
|
}
|
|
};
|
|
|
|
#define for_each_mmc_mode_by_pref(caps, mwt) \
|
|
for (mwt = mmc_modes_by_pref;\
|
|
mwt < mmc_modes_by_pref + ARRAY_SIZE(mmc_modes_by_pref);\
|
|
mwt++) \
|
|
if (caps & MMC_CAP(mwt->mode))
|
|
|
|
static const struct ext_csd_bus_width {
|
|
uint32_t cap;
|
|
int is_ddr;
|
|
uint32_t ext_csd_bits;
|
|
} ext_csd_bus_width[] = {
|
|
{MMC_MODE_8BIT, 1, EXT_CSD_DDR_BUS_WIDTH_8},
|
|
{MMC_MODE_4BIT, 1, EXT_CSD_DDR_BUS_WIDTH_4},
|
|
{MMC_MODE_8BIT, 0, EXT_CSD_BUS_WIDTH_8},
|
|
{MMC_MODE_4BIT, 0, EXT_CSD_BUS_WIDTH_4},
|
|
{MMC_MODE_1BIT, 0, EXT_CSD_BUS_WIDTH_1},
|
|
};
|
|
|
|
#define for_each_supported_width(caps, ddr, ecbv) \
|
|
for (ecbv = ext_csd_bus_width;\
|
|
ecbv < ext_csd_bus_width + ARRAY_SIZE(ext_csd_bus_width);\
|
|
ecbv++) \
|
|
if ((ddr == ecbv->is_ddr) && (caps & ecbv->cap))
|
|
|
|
static int mmc_select_mode_and_width(struct mmc_device *mmc, uint32_t card_caps)
|
|
{
|
|
int err;
|
|
const struct mode_width_tuning *mwt;
|
|
const struct ext_csd_bus_width *ecbw;
|
|
|
|
/* Restrict card's capabilities by what the host can do */
|
|
card_caps &= mmc->host_caps;
|
|
|
|
/* mmcPrintf("card_caps: %x\r\n", card_caps); */
|
|
/* Only version 4 of MMC supports wider bus widths */
|
|
if (mmc->version < MMC_VERSION_4)
|
|
return 0;
|
|
|
|
/*
|
|
* In case the eMMC is in HS200/HS400 mode, downgrade to HS mode
|
|
* before doing anything else, since a transition from either of
|
|
* the HS200/HS400 mode directly to legacy mode is not supported.
|
|
*/
|
|
if (mmc->selected_mode == MMC_HS_200 || mmc->selected_mode == MMC_HS_400)
|
|
mmc_set_card_speed(mmc, MMC_HS, 1);
|
|
else
|
|
mmc_set_clock(mmc, mmc->legacy_speed, 1);
|
|
|
|
for_each_mmc_mode_by_pref(card_caps, mwt) {
|
|
for_each_supported_width(card_caps & mwt->widths,
|
|
mmc_is_mode_ddr(mwt->mode), ecbw) {
|
|
|
|
/* configure the bus width (card + host) */
|
|
err = mmc_switch(mmc, EXT_CSD_BUS_WIDTH, ecbw->ext_csd_bits & ~EXT_CSD_DDR_FLAG);
|
|
/* mmcPrintf("%s[%d]: %d\r\n", __func__, __LINE__, err); */
|
|
if (err)
|
|
goto error;
|
|
mmc_set_bus_width(mmc, bus_width(ecbw->cap));
|
|
|
|
if (mwt->mode == MMC_HS_400) {
|
|
err = mmc_select_hs400(mmc);
|
|
if (err) {
|
|
mmcPrintf("Select HS400 failed %d\r\n", err);
|
|
goto error;
|
|
}
|
|
} else {
|
|
/* configure the bus speed (card) */
|
|
err = mmc_set_card_speed(mmc, mwt->mode, 0);
|
|
if (err)
|
|
goto error;
|
|
|
|
/*
|
|
* configure the bus width AND the ddr mode
|
|
* (card). The host side will be taken care
|
|
* of in the next step
|
|
*/
|
|
if (ecbw->ext_csd_bits & EXT_CSD_DDR_FLAG) {
|
|
err = mmc_switch(mmc, EXT_CSD_BUS_WIDTH, ecbw->ext_csd_bits);
|
|
if (err)
|
|
goto error;
|
|
}
|
|
|
|
/* configure the bus mode (host) */
|
|
mmc_select_mode(mmc, mwt->mode);
|
|
mmc_set_clock(mmc, mmc->tran_speed, 1);
|
|
|
|
/* execute tuning if needed */
|
|
if (mwt->tuning) {
|
|
err = mmc_execute_tuning(mmc, mwt->tuning);
|
|
if (err) {
|
|
mmcPrintf("%s: retraining tuning parameters.\r\n", mmc_device_name(mmc));
|
|
goto error;
|
|
}
|
|
/* tunning success, skip ext_csd checking. */
|
|
if (mmc->quirks & MMC_QUIRK_TUNNING) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* do a transfer to check the configuration */
|
|
err = mmc_read_and_compare_ext_csd(mmc);
|
|
if (!err)
|
|
return 0;
|
|
error:
|
|
/* if an error occured, revert to a safer bus mode */
|
|
mmc_switch(mmc, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_1);
|
|
mmc_select_mode(mmc, MMC_LEGACY);
|
|
mmc_set_bus_width(mmc, 1);
|
|
mmc_set_clock(mmc, mmc->tran_speed, 1);
|
|
}
|
|
}
|
|
|
|
/* mmcPrintf("%s: fallback to legacy mode\r\n", __func__); */
|
|
|
|
return -524;
|
|
}
|
|
|
|
static int mmc_get_capabilities(struct mmc_device *mmc)
|
|
{
|
|
uint8_t *ext_csd = mmc->ext_csd;
|
|
char cardtype;
|
|
|
|
mmc->card_caps |= MMC_MODE_1BIT | MMC_CAP(MMC_LEGACY);
|
|
|
|
/* Only version 4 supports high-speed */
|
|
if (mmc->version < MMC_VERSION_4)
|
|
return 0;
|
|
|
|
mmc->card_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT;
|
|
|
|
cardtype = ext_csd[EXT_CSD_CARD_TYPE];
|
|
mmc->cardtype = cardtype;
|
|
|
|
if (cardtype & (EXT_CSD_CARD_TYPE_HS200_1_2V | EXT_CSD_CARD_TYPE_HS200_1_8V)) {
|
|
mmc->card_caps |= MMC_MODE_HS200;
|
|
}
|
|
if (cardtype & (EXT_CSD_CARD_TYPE_HS400_1_2V | EXT_CSD_CARD_TYPE_HS400_1_8V)) {
|
|
mmc->card_caps |= MMC_MODE_HS400;
|
|
}
|
|
if (cardtype & EXT_CSD_CARD_TYPE_52) {
|
|
if (cardtype & EXT_CSD_CARD_TYPE_DDR_52)
|
|
mmc->card_caps |= MMC_MODE_DDR_52MHz;
|
|
mmc->card_caps |= MMC_MODE_HS_52MHz;
|
|
}
|
|
if (cardtype & EXT_CSD_CARD_TYPE_26)
|
|
mmc->card_caps |= MMC_MODE_HS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* frequency bases */
|
|
/* divided by 10 to be nice to platforms without floating point */
|
|
static const int fbase[] = {
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
};
|
|
/* Multiplier values for TRAN_SPEED. Multiplied by 10 to be nice
|
|
* to platforms without floating point.
|
|
*/
|
|
static const uint8_t multipliers[] = {
|
|
0, /* reserved */
|
|
10,
|
|
12,
|
|
13,
|
|
15,
|
|
20,
|
|
25,
|
|
30,
|
|
35,
|
|
40,
|
|
45,
|
|
50,
|
|
55,
|
|
60,
|
|
70,
|
|
80,
|
|
};
|
|
|
|
static int startup(struct mmc_device *mmc)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
int i, err, version;
|
|
uint64_t cmult, csize;
|
|
uint32_t mult, freq;
|
|
|
|
/* send CMD2, put card to Indentify mode */
|
|
/* Put the Card in Identify Mode */
|
|
cmd.cmdidx = 2;
|
|
cmd.resp_type = MMC_RSP_R2;
|
|
cmd.cmdarg = 0;
|
|
|
|
err = mmc_send_command(mmc, &cmd, NULL);
|
|
if (err && (mmc->quirks & MMC_QUIRK_RETRY_SEND_CID)) {
|
|
int retries = 4;
|
|
/*
|
|
* It has been seen that SEND_CID may fail on the first
|
|
* attempt, let's try a few more time
|
|
*/
|
|
do {
|
|
mmcDelayUs(50000);
|
|
err = mmc_send_command(mmc, &cmd, NULL);
|
|
if (!err)
|
|
break;
|
|
} while (retries--);
|
|
}
|
|
if (err)
|
|
return -1;
|
|
memcpy(mmc->cid, cmd.response, MMC_CID_LEN); /* sizeof (cmd.response) == 16 */
|
|
|
|
/* CMD3: set the Relative Address. */
|
|
cmd.cmdidx = 3;
|
|
cmd.cmdarg = mmc->rca << 16;
|
|
cmd.resp_type = MMC_RSP_R1;
|
|
|
|
err = mmc_send_command(mmc, &cmd, NULL);
|
|
if (err)
|
|
return -2;
|
|
|
|
/* CMD9: Get the Card-Specific Data (*/
|
|
cmd.cmdidx = 9;
|
|
cmd.resp_type = MMC_RSP_R2;
|
|
cmd.cmdarg = mmc->rca << 16;
|
|
|
|
err = mmc_send_command(mmc, &cmd, NULL);
|
|
if (err)
|
|
return err;
|
|
|
|
for (i = 0; i < 4; ++i) {
|
|
mmc->csd[i] = cmd.response[i];
|
|
}
|
|
|
|
if (mmc->version == MMC_VERSION_UNKNOWN) {
|
|
version = (cmd.response[0] >> 26) & 0xf;
|
|
/* mmcPrintf("%s: mmc version %d\r\n", mmc_device_name(mmc), version); */
|
|
switch (version) {
|
|
case 0:
|
|
mmc->version = MMC_VERSION_1_2;
|
|
break;
|
|
case 1:
|
|
mmc->version = MMC_VERSION_1_4;
|
|
break;
|
|
case 2:
|
|
mmc->version = MMC_VERSION_2_2;
|
|
break;
|
|
case 3:
|
|
mmc->version = MMC_VERSION_3;
|
|
break;
|
|
case 4:
|
|
mmc->version = MMC_VERSION_4;
|
|
break;
|
|
case 6:
|
|
mmc->version = MMC_VERSION_5_1;
|
|
break;
|
|
default:
|
|
mmc->version = MMC_VERSION_1_2;
|
|
break;
|
|
}
|
|
}
|
|
/* divide frequency by 10, since the mults are 10x bigger */
|
|
freq = fbase[(cmd.response[0] & 0x7)];
|
|
mult = multipliers[((cmd.response[0] >> 3) & 0xf)];
|
|
|
|
mmc->legacy_speed = freq * mult;
|
|
mmc_select_mode(mmc, MMC_LEGACY);
|
|
|
|
mmc->dsr_imp = ((cmd.response[1] >> 12) & 0x1);
|
|
mmc->read_bl_len = 1 << ((cmd.response[1] >> 16) & 0xf);
|
|
mmc->write_bl_len = 1 << ((cmd.response[3] >> 22) & 0xf);
|
|
|
|
if ((mmc->ocr & OCR_HCS) == OCR_HCS) { /* high capacity */
|
|
csize = (mmc->csd[1] & 0x3f) << 16 | (mmc->csd[2] & 0xffff0000) >> 16;
|
|
cmult = 8;
|
|
} else {
|
|
csize = (mmc->csd[1] & 0x3ff) << 2 | (mmc->csd[2] & 0xc0000000) >> 30;
|
|
cmult = (mmc->csd[2] & 0x00038000) >> 15;
|
|
}
|
|
|
|
mmc->capacity_user = (csize + 1) << (cmult + 2);
|
|
mmc->capacity_user *= mmc->read_bl_len;
|
|
mmc->capacity_boot = 0;
|
|
mmc->capacity_rpmb = 0;
|
|
for (i = 0; i < 4; i++)
|
|
mmc->capacity_gp[i] = 0;
|
|
|
|
if (mmc->read_bl_len > MMC_BLKSIZE)
|
|
mmc->read_bl_len = MMC_BLKSIZE;
|
|
|
|
if (mmc->write_bl_len > MMC_BLKSIZE)
|
|
mmc->write_bl_len = MMC_BLKSIZE;
|
|
|
|
if (mmc->dsr_imp && (0xffffffff != mmc->dsr)) { /* device output driver */
|
|
cmd.cmdidx = 4; /* CMD4: set dsr */
|
|
cmd.cmdarg = ((mmc->dsr & 0xffff) << 16) | 0xffff;
|
|
cmd.resp_type = MMC_RSP_NONE;
|
|
if (mmc_send_command(mmc, &cmd, NULL))
|
|
mmcPrintf("%s: SET_DSR failed\r\n", mmc_device_name(mmc));
|
|
else {
|
|
mmcPrintf("%s: dsr_imp enable, dsr(0x%04x)\r\n", mmc_device_name(mmc), mmc->dsr);
|
|
}
|
|
}
|
|
|
|
/* CMD7: Select the card, and put it into Transfer Mode */
|
|
cmd.cmdidx = 7;
|
|
cmd.resp_type = MMC_RSP_R1;
|
|
cmd.cmdarg = mmc->rca << 16;
|
|
err = mmc_send_command(mmc, &cmd, NULL);
|
|
if (err)
|
|
return -3;
|
|
|
|
mmc->erase_grp_size = 1;
|
|
mmc->part_config = MMCPART_NOAVAILABLE;
|
|
|
|
mmc_startup_v4(mmc);
|
|
|
|
mmcSetCapacity(mmc, 0);
|
|
|
|
mmc_get_capabilities(mmc);
|
|
|
|
err = mmc_select_mode_and_width(mmc, mmc->card_caps);
|
|
if (err) {
|
|
return -4;
|
|
}
|
|
/* switch to trans state */
|
|
mmc_start_xfer(mmc);
|
|
return 0;
|
|
}
|
|
|
|
static int do_init(struct mmc_device *mmc)
|
|
{
|
|
int count = 100;
|
|
|
|
if (goto_idle(mmc)) {
|
|
return -1;
|
|
}
|
|
mmcDelayUs(10000);
|
|
|
|
if (mmc->quirks & MMC_QUIRK_RETRY_IDLE) {
|
|
goto_idle(mmc);
|
|
mmcDelayUs(10000);
|
|
}
|
|
|
|
do {
|
|
if (send_op_cond(mmc)) {
|
|
goto skip;
|
|
}
|
|
if (mmc->ocr & OCR_NOT_BUSY) {
|
|
break;
|
|
}
|
|
skip:
|
|
mmcDelayUs(50000);
|
|
} while (count-- > 0);
|
|
|
|
if (!(mmc->ocr & OCR_NOT_BUSY)) { /* timeout */
|
|
return -3;
|
|
}
|
|
|
|
mmc->rca = 1;
|
|
if (startup(mmc))
|
|
return -4;
|
|
|
|
mmcPrintf("%s: capacity 0x%llx\r\n", mmc_device_name(mmc), mmc->capacity);
|
|
return 0;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{: user level api */
|
|
int mmcBlocksXfer(size_t off , size_t blocks, void *buffer, int (*rwio)(struct mmc_device *, size_t, size_t, void *))
|
|
{
|
|
struct mmc_device *mmc;
|
|
int err;
|
|
size_t blk_save, nblks, lba = 0, lba_end;
|
|
uint8_t *bp = buffer;
|
|
|
|
if (mmcLock() != 0) {
|
|
return -1;
|
|
}
|
|
blk_save = blocks;
|
|
for (mmc = __mmc_dev_list; (mmc != NULL) && (blocks > 0); mmc = mmc->next) {
|
|
lba_end = lba + mmc->capacity / MMC_BLKSIZE;
|
|
if ((off >= lba) && (off < lba_end)) {
|
|
nblks = lba_end - off;
|
|
if (nblks > blocks)
|
|
nblks = blocks;
|
|
if ((err = rwio(mmc, off - lba, nblks, bp)) != 0) {
|
|
mmcPrintf("%s: read off %d blks %d failed\r\n", mmc_device_name(mmc), off, nblks);
|
|
mmcUnlock();
|
|
return err;
|
|
}
|
|
off += nblks;
|
|
blocks -= nblks;
|
|
bp += nblks * MMC_BLKSIZE;
|
|
}
|
|
lba = lba_end;
|
|
}
|
|
mmcUnlock();
|
|
return (blk_save - blocks);
|
|
}
|
|
|
|
int mmcDeviceStart(struct mmc_device *mmc)
|
|
{
|
|
int nret;
|
|
|
|
mmc->dsr_imp = 0;
|
|
mmc->dsr = 0xffffffff;
|
|
|
|
nret = mmc_hwinit(mmc);
|
|
if (nret > 0) { /* already initialized */
|
|
return 0;
|
|
}
|
|
|
|
if (nret < 0) {
|
|
mmc->capacity = 0;
|
|
mmcPrintf("mmc(%s): hwinit failed: %d\r\n", mmc_device_name(mmc), nret);
|
|
return -1;
|
|
}
|
|
|
|
goto_idle(mmc);
|
|
mmcDelayUs(100000);
|
|
nret = do_init(mmc);
|
|
if (nret) {
|
|
mmcPrintf("mmc(%s): init failed %d\r\n", mmc_device_name(mmc), nret);
|
|
mmc->capacity = 0;
|
|
return -2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void mmcDeviceRemoveAll()
|
|
{
|
|
struct mmc_device *mmc;
|
|
uint32_t caps_filtered;
|
|
|
|
if (mmcLock() != 0) {
|
|
return;
|
|
}
|
|
for (mmc = __mmc_dev_list; mmc != NULL; mmc = mmc->next) {
|
|
caps_filtered = mmc->card_caps & ~(MMC_CAP(MMC_HS_200) | MMC_CAP(MMC_HS_400));
|
|
mmc_select_mode_and_width(mmc, caps_filtered);
|
|
if (mmc->driver && mmc->driver->free)
|
|
mmc->driver->free(mmc);
|
|
}
|
|
__mmc_dev_list = NULL;
|
|
__mmc_dev_tail = &__mmc_dev_list;
|
|
mmcUnlock();
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
|