4156 lines
128 KiB
C
4156 lines
128 KiB
C
/**
|
|
* @file sdrv_mmc_sdhci.c
|
|
* @brief sdrv mmc sdhci drv source.
|
|
*
|
|
* @copyright Copyright (c) 2020 Semidrive Semiconductor.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <debug.h>
|
|
#include <common.h>
|
|
#include <compiler.h>
|
|
#include <param.h>
|
|
#include <sdrv_mmc_sdhci.h>
|
|
#include <sdrv_ckgen.h>
|
|
#include <sdrv_sdhci.h>
|
|
#include <part.h>
|
|
#include <sdrv_scr.h>
|
|
|
|
#define SEHC_MAX_TUNING_LOOP 140
|
|
|
|
/* SDHCI SEHC REG */
|
|
#define SDHCI_DLL_CTRL (0x100)
|
|
#define SDHCI_DLL_STATUS (0x104)
|
|
|
|
#define SDHCI_EMMC_CTRL_REG (0x10C)
|
|
|
|
#define SDHCI_TUNING_CTRL_REG (0x118)
|
|
#define SDHCI_TUNING_STATUS_REG (0x11C)
|
|
#define SDHCI_OE_MASK_SEL_REG (0x124)
|
|
|
|
/* dll related macros */
|
|
#define SDHCI_DLL_SLV_TARGET_MASK 0x3f
|
|
#define SDHCI_DLL_SLV_TARGET_OFFSET 0x1
|
|
#define SDHCI_DLL_EN 0x00000001
|
|
|
|
#define SDHCI_DLL_REF_LOCK 0x00000001
|
|
|
|
/* emmc related macros */
|
|
#define SDHCI_RST_N 0x100
|
|
#define SDHCI_FIX_BLK_SIZE 0x20
|
|
#define SDHCI_CLK_STP_BLK_GAP 0x10
|
|
#define SDHCI_EMMC_EXT 0x2
|
|
#define SDHCI_ENHANCE_HS400_EN 0x1
|
|
|
|
/* tuning related macros */
|
|
#define SDHCI_TUNING_CLK_STOP_EN 0x80000000
|
|
#define SDHCI_DELAY_TAP_VALUE_MASK 0x3f
|
|
#define SDHCI_DELAY_TAP_VALUE_OFFSET 0x10
|
|
#define SDHCI_OVERRIDE_VAL_MASK 0x7f
|
|
#define SDHCI_OVERRIDE_VAL_OFFSET 0x9
|
|
#define SDHCI_OVERRIDE_EN 0x00000100
|
|
#define SDHCI_TAP_STEP_MASK 0x3
|
|
#define SDHCI_TAP_STEP_OFFSET 0x6
|
|
#define SDHCI_START_TAP_MASK 0x3f
|
|
#define SDHCI_START_TAP_OFFSET 0x0
|
|
|
|
#define SDHCI_TUNING_TAP_VALUE_MASK 0x7f
|
|
#define SDHCI_TUNING_TAP_VALUE_OFFSET 0x14
|
|
#define SDHCI_FAIL_NUM_MASK 0x7f
|
|
#define SDHCI_FAIL_NUM_OFFSET 0xc
|
|
#define SDHCI_PASS_NUM_MASK 0x7f
|
|
#define SDHCI_PASS_NUM_OFFSET 0x4
|
|
#define SDHCI_SEC_FAIL_FLAG 0x00000008
|
|
#define SDHCI_FIR_PASS_FLAG 0x00000004
|
|
#define SDHCI_FIR_FAIL_FLAG 0x00000002
|
|
#define SDHCI_TUNING_FAIL 0x00000001
|
|
|
|
/* SEHC TUNING related macros */
|
|
#define SEHC_TUNE_CTRL_MIN 0
|
|
#define SEHC_1_0_TUNE_CTRL_MIN 20
|
|
#define SEHC_TUNE_CTRL_MAX 128
|
|
#define SEHC_TUNE_CTRL_STEP 1
|
|
#define SEHC_TUNE_WINDOW_MIN 5
|
|
|
|
/* SDHCI OE related macros */
|
|
#define SDHCI_CMD_OE_MASK_SEL 0x3
|
|
#define SDHCI_CMD_OE_OFFSET 0x0
|
|
#define SDHCI_DAT_OE_MASK_SEL 0xc
|
|
#define SDHCI_DAT_OE_OFFSET 0x2
|
|
|
|
// NOTE: for test on zone, need define WITH_ON_ZONE macro
|
|
//#define WITH_ON_ZONE 1
|
|
|
|
#define USE_TARGET_HS200_CAPS 1
|
|
|
|
/* data access time unit in ns */
|
|
static const uint32_t taac_unit[] = {1, 10, 100, 1000,
|
|
10000, 100000, 1000000, 10000000
|
|
};
|
|
|
|
/* data access time value x 10 */
|
|
static const uint32_t taac_value[] = {0, 10, 12, 13, 15, 20, 25, 30,
|
|
35, 40, 45, 50, 55, 60, 70, 80
|
|
};
|
|
|
|
/* data transfer rate in kbit/s */
|
|
static const uint32_t xfer_rate_unit[] = {100, 1000, 10000, 100000, 0, 0, 0, 0};
|
|
|
|
/* data transfer rate value x 10*/
|
|
static const uint32_t xfer_rate_value[] = {0, 10, 12, 13, 15, 20, 26, 30,
|
|
35, 40, 45, 52, 55, 60, 70, 80
|
|
};
|
|
|
|
#define TUNING_BLK_SIZE 128
|
|
|
|
static uint8_t tuning_data[TUNING_BLK_SIZE] __ALIGNED(CONFIG_ARCH_CACHE_LINE);
|
|
|
|
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,
|
|
};
|
|
|
|
/*
|
|
* Function: mmc decode and save csd
|
|
* Arg : Card structure & raw csd
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Decodes CSD response received from the card.
|
|
* Note that we have defined only few of the CSD elements
|
|
* in csd structure. We'll only decode those values.
|
|
*/
|
|
static status_t sdrv_mmc_decode_and_save_csd(struct mmc_card *card)
|
|
{
|
|
uint32_t mmc_sizeof = 0;
|
|
uint32_t mmc_unit = 0;
|
|
uint32_t mmc_value = 0;
|
|
uint32_t mmc_temp = 0;
|
|
uint32_t *raw_csd = card->raw_csd;
|
|
|
|
struct mmc_csd mmc_csd;
|
|
|
|
mmc_sizeof = sizeof(uint32_t) * 8;
|
|
|
|
mmc_csd.cmmc_structure = UNPACK_BITS(raw_csd, 126, 2, mmc_sizeof);
|
|
|
|
if (MMC_CARD_SD(card)) {
|
|
/* Parse CSD according to SD card spec. */
|
|
|
|
/*
|
|
* CSD register is little bit differnet for CSD version 2.0 High
|
|
* Capacity and CSD version 1.0/2.0 Standard memory cards.
|
|
* In Version 2.0 some of the fields have fixed values and it's
|
|
* not necessary for host to refer these fields in CSD sent by
|
|
* card
|
|
*/
|
|
|
|
if (mmc_csd.cmmc_structure == 1) {
|
|
/* CSD Version 2.0 */
|
|
mmc_csd.card_cmd_class = UNPACK_BITS(raw_csd, 84, 12, mmc_sizeof);
|
|
/* Fixed value is 9 = 2^9 = 512 */
|
|
mmc_csd.write_blk_len = 512;
|
|
/* Fixed value is 9 = 512 */
|
|
mmc_csd.read_blk_len = 512;
|
|
/* Fixed value: 010b */
|
|
mmc_csd.r2w_factor = 0x2;
|
|
/* Not there in version 2.0 */
|
|
mmc_csd.c_size_mult = 0;
|
|
mmc_csd.c_size = UNPACK_BITS(raw_csd, 48, 22, mmc_sizeof);
|
|
mmc_csd.nsac_clk_cycle =
|
|
UNPACK_BITS(raw_csd, 104, 8, mmc_sizeof) * 100;
|
|
|
|
mmc_unit = UNPACK_BITS(raw_csd, 112, 3, mmc_sizeof);
|
|
mmc_value = UNPACK_BITS(raw_csd, 115, 4, mmc_sizeof);
|
|
mmc_csd.taac_ns = 0; /* Unused */
|
|
mmc_csd.taac_clks = 0; /* Unused */
|
|
|
|
mmc_csd.erase_blk_len = 1;
|
|
mmc_csd.read_blk_misalign = 0;
|
|
mmc_csd.write_blk_misalign = 0;
|
|
mmc_csd.read_blk_partial = 0;
|
|
mmc_csd.write_blk_partial = 0;
|
|
|
|
mmc_unit = UNPACK_BITS(raw_csd, 96, 3, mmc_sizeof);
|
|
mmc_value = UNPACK_BITS(raw_csd, 99, 4, mmc_sizeof);
|
|
mmc_csd.tran_speed =
|
|
(xfer_rate_value[mmc_value] * xfer_rate_unit[mmc_unit]) / 10;
|
|
|
|
mmc_csd.wp_grp_size = 0x0;
|
|
mmc_csd.wp_grp_enable = 0x0;
|
|
mmc_csd.perm_wp = UNPACK_BITS(raw_csd, 13, 1, mmc_sizeof);
|
|
mmc_csd.temp_wp = UNPACK_BITS(raw_csd, 12, 1, mmc_sizeof);
|
|
|
|
/* Calculate the card capcity */
|
|
card->capacity =
|
|
(unsigned long long)(1 + mmc_csd.c_size) * 512 * 1024;
|
|
}
|
|
else {
|
|
/* CSD Version 1.0 */
|
|
mmc_csd.card_cmd_class = UNPACK_BITS(raw_csd, 84, 12, mmc_sizeof);
|
|
|
|
mmc_temp = UNPACK_BITS(raw_csd, 22, 4, mmc_sizeof);
|
|
mmc_csd.write_blk_len =
|
|
(mmc_temp > 8 && mmc_temp < 12) ? (1 << mmc_temp) : 512;
|
|
|
|
mmc_temp = UNPACK_BITS(raw_csd, 80, 4, mmc_sizeof);
|
|
mmc_csd.read_blk_len =
|
|
(mmc_temp > 8 && mmc_temp < 12) ? (1 << mmc_temp) : 512;
|
|
|
|
mmc_unit = UNPACK_BITS(raw_csd, 112, 3, mmc_sizeof);
|
|
mmc_value = UNPACK_BITS(raw_csd, 115, 4, mmc_sizeof);
|
|
mmc_csd.taac_ns =
|
|
(taac_value[mmc_value] * taac_unit[mmc_unit] + 9) / 10;
|
|
mmc_csd.taac_clks = UNPACK_BITS(raw_csd, 104, 8, mmc_sizeof) * 100;
|
|
|
|
mmc_unit = UNPACK_BITS(raw_csd, 96, 3, mmc_sizeof);
|
|
mmc_value = UNPACK_BITS(raw_csd, 99, 4, mmc_sizeof);
|
|
mmc_csd.tran_speed =
|
|
(xfer_rate_value[mmc_value] * xfer_rate_unit[mmc_unit]) / 10;
|
|
|
|
mmc_csd.nsac_clk_cycle =
|
|
UNPACK_BITS(raw_csd, 104, 8, mmc_sizeof) * 100;
|
|
|
|
mmc_csd.r2w_factor = UNPACK_BITS(raw_csd, 26, 3, mmc_sizeof);
|
|
mmc_csd.sector_size = UNPACK_BITS(raw_csd, 39, 7, mmc_sizeof) + 1;
|
|
|
|
mmc_csd.erase_blk_len = UNPACK_BITS(raw_csd, 46, 1, mmc_sizeof);
|
|
mmc_csd.read_blk_misalign = UNPACK_BITS(raw_csd, 77, 1, mmc_sizeof);
|
|
mmc_csd.write_blk_misalign =
|
|
UNPACK_BITS(raw_csd, 78, 1, mmc_sizeof);
|
|
mmc_csd.read_blk_partial = UNPACK_BITS(raw_csd, 79, 1, mmc_sizeof);
|
|
mmc_csd.write_blk_partial = UNPACK_BITS(raw_csd, 21, 1, mmc_sizeof);
|
|
|
|
mmc_csd.c_size_mult = UNPACK_BITS(raw_csd, 47, 3, mmc_sizeof);
|
|
mmc_csd.c_size = UNPACK_BITS(raw_csd, 62, 12, mmc_sizeof);
|
|
mmc_csd.wp_grp_size = UNPACK_BITS(raw_csd, 32, 7, mmc_sizeof);
|
|
mmc_csd.wp_grp_enable = UNPACK_BITS(raw_csd, 31, 1, mmc_sizeof);
|
|
mmc_csd.perm_wp = UNPACK_BITS(raw_csd, 13, 1, mmc_sizeof);
|
|
mmc_csd.temp_wp = UNPACK_BITS(raw_csd, 12, 1, mmc_sizeof);
|
|
|
|
/* Calculate the card capacity */
|
|
mmc_temp = (1 << (mmc_csd.c_size_mult + 2)) * (mmc_csd.c_size + 1);
|
|
card->capacity =
|
|
(unsigned long long)mmc_temp * mmc_csd.read_blk_len;
|
|
}
|
|
}
|
|
else {
|
|
/* Parse CSD according to MMC card spec. */
|
|
mmc_csd.spec_vers = UNPACK_BITS(raw_csd, 122, 4, mmc_sizeof);
|
|
mmc_csd.card_cmd_class = UNPACK_BITS(raw_csd, 84, 12, mmc_sizeof);
|
|
mmc_csd.write_blk_len = 1 << UNPACK_BITS(raw_csd, 22, 4, mmc_sizeof);
|
|
mmc_csd.read_blk_len = 1 << UNPACK_BITS(raw_csd, 80, 4, mmc_sizeof);
|
|
mmc_csd.r2w_factor = UNPACK_BITS(raw_csd, 26, 3, mmc_sizeof);
|
|
mmc_csd.c_size_mult = UNPACK_BITS(raw_csd, 47, 3, mmc_sizeof);
|
|
mmc_csd.c_size = UNPACK_BITS(raw_csd, 62, 12, mmc_sizeof);
|
|
mmc_csd.nsac_clk_cycle = UNPACK_BITS(raw_csd, 104, 8, mmc_sizeof) * 100;
|
|
|
|
mmc_unit = UNPACK_BITS(raw_csd, 112, 3, mmc_sizeof);
|
|
mmc_value = UNPACK_BITS(raw_csd, 115, 4, mmc_sizeof);
|
|
mmc_csd.taac_ns = (taac_value[mmc_value] * taac_unit[mmc_unit] + 9) / 10;
|
|
mmc_csd.taac_clks = UNPACK_BITS(raw_csd, 104, 8, mmc_sizeof) * 100;
|
|
|
|
mmc_csd.read_blk_misalign = UNPACK_BITS(raw_csd, 77, 1, mmc_sizeof);
|
|
mmc_csd.write_blk_misalign = UNPACK_BITS(raw_csd, 78, 1, mmc_sizeof);
|
|
mmc_csd.read_blk_partial = UNPACK_BITS(raw_csd, 79, 1, mmc_sizeof);
|
|
mmc_csd.write_blk_partial = UNPACK_BITS(raw_csd, 21, 1, mmc_sizeof);
|
|
|
|
/* Ignore -- no use of this value. */
|
|
mmc_csd.tran_speed = 0x00;
|
|
|
|
mmc_csd.erase_grp_size = UNPACK_BITS(raw_csd, 42, 5, mmc_sizeof);
|
|
mmc_csd.erase_grp_mult = UNPACK_BITS(raw_csd, 37, 5, mmc_sizeof);
|
|
mmc_csd.wp_grp_size = UNPACK_BITS(raw_csd, 32, 5, mmc_sizeof);
|
|
mmc_csd.wp_grp_enable = UNPACK_BITS(raw_csd, 31, 1, mmc_sizeof);
|
|
mmc_csd.perm_wp = UNPACK_BITS(raw_csd, 13, 1, mmc_sizeof);
|
|
mmc_csd.temp_wp = UNPACK_BITS(raw_csd, 12, 1, mmc_sizeof);
|
|
|
|
/* Calculate the card capcity */
|
|
if (mmc_csd.c_size != 0xFFF) {
|
|
/* For cards less than or equal to 2GB */
|
|
mmc_temp = (1 << (mmc_csd.c_size_mult + 2)) * (mmc_csd.c_size + 1);
|
|
card->capacity =
|
|
(unsigned long long)mmc_temp * mmc_csd.read_blk_len;
|
|
}
|
|
else {
|
|
/* For cards greater than 2GB, Ext CSD register's SEC_COUNT
|
|
* is used to calculate the size.
|
|
*/
|
|
uint64_t sec_count;
|
|
|
|
sec_count =
|
|
(card->ext_csd[MMC_SEC_COUNT4] << MMC_SEC_COUNT4_SHIFT) |
|
|
(card->ext_csd[MMC_SEC_COUNT3] << MMC_SEC_COUNT3_SHIFT) |
|
|
(card->ext_csd[MMC_SEC_COUNT2] << MMC_SEC_COUNT2_SHIFT) |
|
|
card->ext_csd[MMC_SEC_COUNT1];
|
|
card->capacity = sec_count * MMC_BLK_SZ;
|
|
}
|
|
}
|
|
|
|
/* save the information in card structure */
|
|
memcpy(&card->csd, &mmc_csd, sizeof(struct mmc_csd));
|
|
|
|
/* Calculate the wp grp size */
|
|
if (MMC_CARD_MMC(card)) {
|
|
if (card->ext_csd[MMC_ERASE_GRP_DEF])
|
|
card->wp_grp_size = MMC_HC_ERASE_MULT *
|
|
card->ext_csd[MMC_HC_ERASE_GRP_SIZE] /
|
|
MMC_BLK_SZ;
|
|
else
|
|
card->wp_grp_size = (card->csd.wp_grp_size + 1) *
|
|
(card->csd.erase_grp_size + 1) *
|
|
(card->csd.erase_grp_mult + 1);
|
|
|
|
card->boot_size = BOOT_PART_MIN_SIZE * card->ext_csd[BOOT_SIZE_MULT];
|
|
card->rpmb_size = RPMB_PART_MIN_SIZE * card->ext_csd[RPMB_SIZE_MULT];
|
|
card->rel_wr_count = card->ext_csd[REL_WR_SEC_C];
|
|
}
|
|
else {
|
|
card->wp_grp_size = (card->csd.wp_grp_size + 1) *
|
|
(card->csd.erase_grp_size + 1) *
|
|
(card->csd.erase_grp_mult + 1);
|
|
}
|
|
|
|
ssdk_printf(SSDK_INFO, "Decoded CSD fields:\r\n");
|
|
ssdk_printf(SSDK_INFO, "cmmc_structure: %u\r\n", mmc_csd.cmmc_structure);
|
|
ssdk_printf(SSDK_INFO, "card_cmd_class: %x\r\n", mmc_csd.card_cmd_class);
|
|
ssdk_printf(SSDK_INFO, "write_blk_len: %u\r\n", mmc_csd.write_blk_len);
|
|
ssdk_printf(SSDK_INFO, "read_blk_len: %u\r\n", mmc_csd.read_blk_len);
|
|
ssdk_printf(SSDK_INFO, "r2w_factor: %u\r\n", mmc_csd.r2w_factor);
|
|
ssdk_printf(SSDK_INFO, "sector_size: %u\r\n", mmc_csd.sector_size);
|
|
ssdk_printf(SSDK_INFO, "c_size_mult:%u\r\n", mmc_csd.c_size_mult);
|
|
ssdk_printf(SSDK_INFO, "c_size: %u\r\n", mmc_csd.c_size);
|
|
ssdk_printf(SSDK_INFO, "nsac_clk_cycle: %u\r\n", mmc_csd.nsac_clk_cycle);
|
|
ssdk_printf(SSDK_INFO, "taac_ns: %u\r\n", mmc_csd.taac_ns);
|
|
ssdk_printf(SSDK_INFO, "taac_clks: %u\r\n", mmc_csd.taac_clks);
|
|
ssdk_printf(SSDK_INFO, "tran_speed: %u kbps\r\n", mmc_csd.tran_speed);
|
|
ssdk_printf(SSDK_INFO, "erase_blk_len: %u\r\n", mmc_csd.erase_blk_len);
|
|
ssdk_printf(SSDK_INFO, "read_blk_misalign: %u\r\n", mmc_csd.read_blk_misalign);
|
|
ssdk_printf(SSDK_INFO, "write_blk_misalign: %u\r\n",
|
|
mmc_csd.write_blk_misalign);
|
|
ssdk_printf(SSDK_INFO, "read_blk_partial: %u\r\n", mmc_csd.read_blk_partial);
|
|
ssdk_printf(SSDK_INFO, "write_blk_partial: %u\r\n", mmc_csd.write_blk_partial);
|
|
ssdk_printf(SSDK_INFO, "wp_grp_size: %u\r\n", card->wp_grp_size);
|
|
ssdk_printf(SSDK_INFO, "Card Capacity: %llu Bytes\r\n", card->capacity);
|
|
|
|
return SDRV_STATUS_OK;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc decode & save cid
|
|
* Arg : card structure & raw cid
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Decode CID sent by the card.
|
|
*/
|
|
static status_t sdrv_mmc_decode_and_save_cid(struct mmc_card *card,
|
|
uint32_t *raw_cid)
|
|
{
|
|
struct mmc_cid mmc_cid;
|
|
uint32_t mmc_sizeof = 0;
|
|
int i = 0;
|
|
|
|
if (!raw_cid) {
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
mmc_sizeof = sizeof(uint32_t) * 8;
|
|
|
|
if (MMC_CARD_SD(card)) {
|
|
mmc_cid.mid = UNPACK_BITS(raw_cid, 120, 8, mmc_sizeof);
|
|
mmc_cid.oid = UNPACK_BITS(raw_cid, 104, 16, mmc_sizeof);
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
mmc_cid.pnm[i] = (uint8_t)UNPACK_BITS(raw_cid, (104 - 8 * (i + 1)),
|
|
8, mmc_sizeof);
|
|
}
|
|
|
|
mmc_cid.pnm[5] = 0;
|
|
mmc_cid.pnm[6] = 0;
|
|
|
|
mmc_cid.prv = UNPACK_BITS(raw_cid, 56, 8, mmc_sizeof);
|
|
mmc_cid.psn = UNPACK_BITS(raw_cid, 24, 32, mmc_sizeof);
|
|
mmc_cid.month = UNPACK_BITS(raw_cid, 8, 4, mmc_sizeof);
|
|
mmc_cid.year = UNPACK_BITS(raw_cid, 12, 8, mmc_sizeof);
|
|
mmc_cid.year += 2000;
|
|
}
|
|
else {
|
|
mmc_cid.mid = UNPACK_BITS(raw_cid, 120, 8, mmc_sizeof);
|
|
mmc_cid.oid = UNPACK_BITS(raw_cid, 104, 16, mmc_sizeof);
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
mmc_cid.pnm[i] = (uint8_t)UNPACK_BITS(raw_cid, (104 - 8 * (i + 1)),
|
|
8, mmc_sizeof);
|
|
}
|
|
|
|
mmc_cid.pnm[6] = 0;
|
|
|
|
mmc_cid.prv = UNPACK_BITS(raw_cid, 48, 8, mmc_sizeof);
|
|
mmc_cid.psn = UNPACK_BITS(raw_cid, 16, 32, mmc_sizeof);
|
|
mmc_cid.month = UNPACK_BITS(raw_cid, 8, 4, mmc_sizeof);
|
|
mmc_cid.year = UNPACK_BITS(raw_cid, 12, 4, mmc_sizeof);
|
|
mmc_cid.year += 1997;
|
|
}
|
|
|
|
/* save it in card database */
|
|
memcpy(&card->cid, &mmc_cid, sizeof(struct mmc_cid));
|
|
|
|
ssdk_printf(SSDK_INFO, "Decoded CID fields:\r\n");
|
|
ssdk_printf(SSDK_INFO, "Manufacturer ID: %x\r\n", mmc_cid.mid);
|
|
ssdk_printf(SSDK_INFO, "OEM ID: 0x%x\r\n", mmc_cid.oid);
|
|
ssdk_printf(SSDK_INFO, "Product Name: %s\r\n", mmc_cid.pnm);
|
|
ssdk_printf(SSDK_INFO, "Product revision: %d.%d\r\n", (mmc_cid.prv >> 4),
|
|
(mmc_cid.prv & 0xF));
|
|
ssdk_printf(SSDK_INFO, "Product serial number: %X\r\n", mmc_cid.psn);
|
|
ssdk_printf(SSDK_INFO, "Manufacturing date: %d %d\r\n", mmc_cid.month,
|
|
mmc_cid.year);
|
|
|
|
return SDRV_STATUS_OK;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc reset cards
|
|
* Arg : host structure
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Reset all the cards to idle condition (CMD 0)
|
|
*/
|
|
static status_t sdrv_mmc_reset_card(struct sdhci_host *host)
|
|
{
|
|
struct mmc_command cmd;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
cmd.cmd_index = CMD0_GO_IDLE_STATE;
|
|
#ifdef WITH_ON_ZONE
|
|
/* if on zone, need use argument for go to pre-idel state */
|
|
cmd.argument = 0xF0F0F0F0;
|
|
#else
|
|
cmd.argument = 0;
|
|
#endif
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_NONE;
|
|
|
|
/* send command */
|
|
return sdrv_sdhci_send_command(host, &cmd);
|
|
}
|
|
|
|
/*
|
|
* Function: mmc operations command
|
|
* Arg : host & card structure
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Send CMD1 to know whether the card supports host VDD profile or
|
|
* not.
|
|
*/
|
|
static status_t sdrv_mmc_send_op_cond(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
struct mmc_command cmd;
|
|
uint32_t mmc_resp = 0;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
uint32_t mmc_retry = 0;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD1 format:
|
|
* [31] Busy bit
|
|
* [30:29] Access mode
|
|
* [28:24] reserved
|
|
* [23:15] 2.7-3.6
|
|
* [14:8] 2.0-2.6
|
|
* [7] 1.7-1.95
|
|
* [6:0] reserved
|
|
*/
|
|
|
|
cmd.cmd_index = CMD1_SEND_OP_COND;
|
|
cmd.argument = card->ocr;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R3;
|
|
|
|
do {
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
/* Command returned success, now it's time to examine response */
|
|
mmc_resp = cmd.resp[0];
|
|
|
|
/* Check the response for busy status */
|
|
if (!(mmc_resp & MMC_OCR_BUSY)) {
|
|
mmc_retry++;
|
|
udelay(1);
|
|
continue;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
while (mmc_retry < MMC_MAX_COMMAND_RETRY);
|
|
|
|
/* If we reached here after max retries, we failed to get OCR */
|
|
if (mmc_retry == MMC_MAX_COMMAND_RETRY && !(mmc_resp & MMC_OCR_BUSY)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Card has busy status set. Init did not complete\r\n");
|
|
return SDRV_STATUS_BUSY;
|
|
}
|
|
|
|
/* Response contains card's ocr. Update card's information */
|
|
card->ocr = mmc_resp;
|
|
|
|
if (mmc_resp & MMC_OCR_SEC_MODE)
|
|
card->type = MMC_TYPE_MMCHC;
|
|
else
|
|
card->type = MMC_TYPE_STD_MMC;
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc send cid
|
|
* Arg : host & card structure
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Request any card to send its uniquie card identification
|
|
* (CID) number (CMD2).
|
|
*/
|
|
static status_t sdrv_mmc_all_send_cid(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
struct mmc_command cmd;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD2 Format:
|
|
* [31:0] stuff bits
|
|
*/
|
|
cmd.cmd_index = CMD2_ALL_SEND_CID;
|
|
cmd.argument = 0;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R2;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Response contains card's 128 bits CID register */
|
|
mmc_ret = sdrv_mmc_decode_and_save_cid(card, cmd.resp);
|
|
|
|
if (mmc_ret) {
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc send relative address
|
|
* Arg : host & card structure
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Ask card to send it's relative card address (RCA).
|
|
* This RCA number is shorter than CID and is used by
|
|
* the host to address the card in future (CMD3)
|
|
*/
|
|
static status_t sdrv_mmc_send_relative_address(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
struct mmc_command cmd;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD3 Format:
|
|
* [31:0] stuff bits
|
|
*/
|
|
if (MMC_CARD_SD(card)) {
|
|
cmd.cmd_index = CMD3_SEND_RELATIVE_ADDR;
|
|
cmd.argument = 0;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R6;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
/* For sD, card will send RCA. Store it */
|
|
card->rca = (cmd.resp[0] >> 16);
|
|
}
|
|
else {
|
|
cmd.cmd_index = CMD3_SEND_RELATIVE_ADDR;
|
|
cmd.argument = (MMC_RCA << 16);
|
|
card->rca = (cmd.argument >> 16);
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R6;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc send csd
|
|
* Arg : host, card structure & o/p arg to store csd
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Requests card to send it's CSD register's contents. (CMD9)
|
|
*/
|
|
static status_t sdrv_mmc_send_csd(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
struct mmc_command cmd;
|
|
uint32_t mmc_arg = 0;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD9 Format:
|
|
* [31:16] RCA
|
|
* [15:0] stuff bits
|
|
*/
|
|
mmc_arg |= card->rca << 16;
|
|
|
|
cmd.cmd_index = CMD9_SEND_CSD;
|
|
cmd.argument = mmc_arg;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R2;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
/* response contains the card csd */
|
|
memcpy(card->raw_csd, cmd.resp, sizeof(cmd.resp));
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc select card
|
|
* Arg : host, card structure
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Selects a card by sending CMD7 to the card with its RCA.
|
|
* If RCA field is set as 0 ( or any other address ),
|
|
* the card will be de-selected. (CMD7)
|
|
*/
|
|
static status_t sdrv_mmc_select_card(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
struct mmc_command cmd;
|
|
uint32_t mmc_arg = 0;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD7 Format:
|
|
* [31:16] RCA
|
|
* [15:0] stuff bits
|
|
*/
|
|
mmc_arg |= card->rca << 16;
|
|
|
|
cmd.cmd_index = CMD7_SELECT_DESELECT_CARD;
|
|
cmd.argument = mmc_arg;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* If we are deselecting card, we do not get response */
|
|
if (card->rca) {
|
|
if (MMC_CARD_SD(card))
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1B;
|
|
else
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
}
|
|
else
|
|
cmd.resp_type = SDHCI_CMD_RESP_NONE;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* Function: mmc set block len
|
|
* Arg : host, card structure & block length
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Send command to set block length.
|
|
*/
|
|
static uint32_t sdrv_mmc_set_block_len(struct sdhci_host *host,
|
|
struct mmc_card *card, uint32_t block_len)
|
|
{
|
|
struct mmc_command cmd;
|
|
uint32_t mmc_ret = 0;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD16 Format:
|
|
* [31:0] block length
|
|
*/
|
|
|
|
cmd.cmd_index = CMD16_SET_BLOCKLEN;
|
|
cmd.argument = block_len;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
/*
|
|
* If blocklength is larger than 512 bytes,
|
|
* the card sets BLOCK_LEN_ERROR bit.
|
|
*/
|
|
if (cmd.resp[0] & MMC_R1_BLOCK_LEN_ERR) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "The block length is not supported by the card\r\n");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Function: mmc get card status
|
|
* Arg : host, card structure & o/p argument card status
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Get the current status of the card
|
|
*/
|
|
static status_t sdrv_mmc_get_card_status(struct sdhci_host *host,
|
|
struct mmc_card *card, uint32_t *status)
|
|
{
|
|
struct mmc_command cmd;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD13 Format:
|
|
* [31:16] RCA
|
|
* [15:0] stuff bits
|
|
*/
|
|
cmd.cmd_index = CMD13_SEND_STATUS;
|
|
cmd.argument = card->rca << 16;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
/* Checking ADDR_OUT_OF_RANGE error in CMD13 response */
|
|
if ((cmd.resp[0] >> 31) & 0x01) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "CMD13 response ADDR_OUT_OF_RANGE failed\r\n");
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
*status = cmd.resp[0];
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc get ext csd
|
|
* Arg : host, card structure & array to hold ext attributes
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Send ext csd command & get the card attributes
|
|
*/
|
|
static status_t sdrv_mmc_get_ext_csd(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
struct mmc_command cmd;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
#if WITH_KERNEL_VM
|
|
void *vptr;
|
|
uint8_t align_pow2 = log2_uint(CONFIG_ARCH_CACHE_LINE);
|
|
|
|
if (vmm_alloc_contiguous(vmm_get_kernel_aspace(), "mmc_ext",
|
|
EXT_CSD_BUF_SIZE, &vptr, align_pow2, 0,
|
|
ARCH_MMU_FLAG_CACHED) < 0) {
|
|
printf("Failed to allocate ext csd buf\r\n");
|
|
return SDRV_STATUS_FAIL;
|
|
}
|
|
|
|
card->ext_csd = vptr;
|
|
#endif
|
|
|
|
ASSERT(card->ext_csd);
|
|
|
|
memset(card->ext_csd, 0, EXT_CSD_BUF_SIZE);
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD8 */
|
|
cmd.cmd_index = CMD8_SEND_EXT_CSD;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.data.data_ptr = card->ext_csd;
|
|
cmd.data.num_blocks = 1;
|
|
cmd.data_present = 0x1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc switch command
|
|
* Arg : Host, card structure, access mode, index & value to be set
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Send switch command to the card to set the ext attribute @ index
|
|
*/
|
|
static status_t sdrv_mmc_switch_cmd(struct sdhci_host *host,
|
|
struct mmc_card *card,
|
|
uint32_t access, uint32_t index, uint32_t value)
|
|
{
|
|
|
|
struct mmc_command cmd;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
uint32_t mmc_status;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/*
|
|
* CMD6 Format:
|
|
* [31:26] set to 0
|
|
* [25:24] access
|
|
* [23:16] index
|
|
* [15:8] value
|
|
* [7:3] set to 0
|
|
* [2:0] cmd set
|
|
*/
|
|
cmd.cmd_index = CMD6_SWITCH_FUNC;
|
|
cmd.argument |= (access << 24);
|
|
cmd.argument |= (index << 16);
|
|
cmd.argument |= (value << 8);
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1B;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "CMD6 send failed\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
if (index == MMC_EXT_MMC_HS_TIMING && value == SDHCI_EMMC_HISPEED_MODE) {
|
|
/* Set the high speed mode in controller */
|
|
if (host->ops->set_uhs_mode)
|
|
host->ops->set_uhs_mode(host, SDHCI_EMMC_HISPEED_MODE);
|
|
|
|
if (host->ops->set_clock)
|
|
mmc_ret = host->ops->set_clock(host, MMC_CLK_50MHZ);
|
|
}
|
|
|
|
/* Check if the card completed the switch command processing */
|
|
mmc_ret = sdrv_mmc_get_card_status(host, card, &mmc_status);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Get card status failed\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
if (MMC_CARD_STATUS(mmc_status) != MMC_TRAN_STATE) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Switch cmd failed. Card not in tran state %x\r\n",
|
|
mmc_status);
|
|
mmc_ret = SDRV_STATUS_MMC_CARD_STATUS_ERR;
|
|
}
|
|
|
|
if (mmc_status & MMC_SWITCH_FUNC_ERR_FLAG) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Switch cmd failed. Switch Error.\r\n");
|
|
mmc_ret = SDRV_STATUS_MMC_SWITCH_FUNC_ERR;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
bool sdrv_mmc_set_drv_type(struct sdhci_host *host, struct mmc_card *card,
|
|
uint8_t drv_type)
|
|
{
|
|
uint32_t ret = 0;
|
|
bool drv_type_changed = false;
|
|
|
|
uint32_t value = ((drv_type << 4) | MMC_HS200_TIMING);
|
|
|
|
if (card->ext_csd[MMC_EXT_MMC_DRV_STRENGTH] & (1 << drv_type))
|
|
ret = sdrv_mmc_switch_cmd(host, card, MMC_ACCESS_WRITE,
|
|
MMC_EXT_MMC_HS_TIMING, value);
|
|
|
|
if (!ret)
|
|
drv_type_changed = true;
|
|
|
|
return drv_type_changed;
|
|
}
|
|
/*
|
|
* Function: mmc set bus width
|
|
* Arg : Host, card structure & width
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Send switch command to set bus width
|
|
*/
|
|
static status_t sdrv_mmc_set_bus_width(struct sdhci_host *host,
|
|
struct mmc_card *card, uint32_t width)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
mmc_ret = sdrv_mmc_switch_cmd(host, card, MMC_ACCESS_WRITE,
|
|
MMC_EXT_MMC_BUS_WIDTH, width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Switch cmd failed\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc card supports hs400 mode
|
|
* Arg : None
|
|
* Return : 1 if hs400 mode is supported, 0 otherwise
|
|
* Flow : Check the ext csd attributes of the card
|
|
*/
|
|
static uint8_t sdrv_mmc_card_supports_hs400_mode(struct mmc_card *card)
|
|
{
|
|
if (card->ext_csd[MMC_DEVICE_TYPE] & MMC_HS_HS400_MODE)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc card supports hs200 mode
|
|
* Arg : None
|
|
* Return : 1 if HS200 mode is supported, 0 otherwise
|
|
* Flow : Check the ext csd attributes of the card
|
|
*/
|
|
static uint8_t sdrv_mmc_card_supports_hs200_mode(struct mmc_card *card)
|
|
{
|
|
if (card->ext_csd[MMC_DEVICE_TYPE] & MMC_HS_HS200_MODE)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc card supports ddr mode
|
|
* Arg : None
|
|
* Return : 1 if DDR mode is supported, 0 otherwise
|
|
* Flow : Check the ext csd attributes of the card
|
|
*/
|
|
static uint8_t sdrv_mmc_card_supports_ddr_mode(struct mmc_card *card)
|
|
{
|
|
if (card->ext_csd[MMC_DEVICE_TYPE] & MMC_HS_DDR_MODE)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function : Enable HS200 mode
|
|
* Arg : Host, card structure and bus width
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow :
|
|
* - Set the bus width to 4/8 bit SDR as supported by the target &
|
|
* host
|
|
* - Set the HS_TIMING on ext_csd 185 for the card
|
|
*/
|
|
static status_t sdrv_mmc_set_hs200_mode(struct sdhci_host *host,
|
|
struct mmc_card *card, uint32_t width)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
DETAIL_PRINTF(SSDK_DEBUG, "\r\n Enabling HS200 Mode Start\r\n");
|
|
|
|
/* Set 4/8 bit SDR bus width */
|
|
mmc_ret = sdrv_mmc_set_bus_width(host, card, width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure to set wide bus for Card(RCA:%x)\r\n",
|
|
card->rca);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Setting HS200 in HS_TIMING using EXT_CSD (CMD6) */
|
|
mmc_ret = sdrv_mmc_switch_cmd(host, card, MMC_ACCESS_WRITE,
|
|
MMC_EXT_MMC_HS_TIMING, MMC_HS200_TIMING);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Switch cmd returned failure %d\r\n", __LINE__);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Enable HS200 mode in controller */
|
|
MMC_SAVE_TIMING(host, SDHCI_EMMC_HS200_MODE);
|
|
|
|
if (host->ops->set_uhs_mode)
|
|
host->ops->set_uhs_mode(host, SDHCI_EMMC_HS200_MODE);
|
|
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, MMC_CLK_200MHZ);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Execute Tuning for hs200 mode */
|
|
if ((mmc_ret = sdrv_sdhci_execute_tuning(host, CMD21_SEND_TUNING_BLOCK, width)))
|
|
DETAIL_PRINTF(SSDK_CRIT, "Tuning for hs200 failed\r\n");
|
|
|
|
DETAIL_PRINTF(SSDK_DEBUG, "\r\n Enabling HS200 Mode Done\r\n");
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc set ddr mode
|
|
* Arg : Host, card structure and bus width
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Set bus width for ddr mode & set controller in DDR mode
|
|
*/
|
|
static status_t sdrv_mmc_set_ddr_mode(struct sdhci_host *host,
|
|
struct mmc_card *card,
|
|
uint32_t width)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
DETAIL_PRINTF(SSDK_DEBUG, "\r\n Enabling DDR Mode Start\r\n");
|
|
|
|
/* Set 4/8 bit DDR bus width */
|
|
if (width == MMC_DATA_BUS_WIDTH_4BIT)
|
|
mmc_ret = sdrv_mmc_set_bus_width(host, card, MMC_DATA_DDR_BUS_WIDTH_4BIT);
|
|
else if (width == MMC_DATA_BUS_WIDTH_8BIT)
|
|
mmc_ret = sdrv_mmc_set_bus_width(host, card, MMC_DATA_DDR_BUS_WIDTH_8BIT);
|
|
else {
|
|
DETAIL_PRINTF(SSDK_CRIT, "DDR mode not suport bus width: %u\r\n", width);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure to set DDR mode for Card(RCA:%x)\r\n",
|
|
card->rca);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Save the timing value, before changing the clock */
|
|
MMC_SAVE_TIMING(host, SDHCI_EMMC_DDR52_MODE);
|
|
|
|
/* Set the DDR mode in controller */
|
|
if (host->ops->set_uhs_mode)
|
|
host->ops->set_uhs_mode(host, SDHCI_EMMC_DDR52_MODE);
|
|
|
|
if (host->ops->set_clock)
|
|
mmc_ret = host->ops->set_clock(host, MMC_CLK_50MHZ);
|
|
|
|
DETAIL_PRINTF(SSDK_DEBUG, "\r\n Enabling DDR Mode Done\r\n");
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc set high speed interface
|
|
* Arg : Host & card structure
|
|
* Return : None
|
|
* Flow : Sets the host uhs mode & clock
|
|
* Adjust the interface speed to optimal speed
|
|
*/
|
|
static status_t sdrv_mmc_set_hs_interface(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
/* Setting HS_TIMING in EXT_CSD (CMD6) */
|
|
mmc_ret = sdrv_mmc_switch_cmd(host, card, MMC_ACCESS_WRITE,
|
|
MMC_EXT_MMC_HS_TIMING, MMC_HS_TIMING);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Switch cmd returned failure %d\r\n", __LINE__);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Save the timing value, before changing the clock */
|
|
MMC_SAVE_TIMING(host, SDHCI_EMMC_HISPEED_MODE);
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function : Enable HS400 mode
|
|
* Arg : Host, card structure and bus width
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow :
|
|
* - Set the bus width to 8 bit DDR
|
|
* - Set the HS_TIMING on ext_csd 185 for the card
|
|
*/
|
|
status_t sdrv_mmc_set_hs400_mode(struct sdhci_host *host, struct mmc_card *card,
|
|
uint32_t width)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
/*
|
|
* Emmc 5.0 spec does not allow changing to hs400 mode directly
|
|
* Need to follow the sequence to change to hs400 mode
|
|
* 1. Enable HS200 mode, perform tuning
|
|
* 2. Change to high speed mode
|
|
* 3. Enable DDR mode
|
|
* 4. Enable HS400 mode, note that hs400 tuning is done in enable hs200 step
|
|
*/
|
|
|
|
DETAIL_PRINTF(SSDK_DEBUG, "\r\n Enabling HS400 Mode Start\r\n");
|
|
|
|
/* HS400 mode is supported only in DDR 8-bit */
|
|
if (width != MMC_DATA_BUS_WIDTH_8BIT) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Bus width is not 8-bit, cannot switch to hs400: %u\r\n",
|
|
width);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
/* 1.Enable HS200 mode */
|
|
mmc_ret = sdrv_mmc_set_hs200_mode(host, card, width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure Setting HS200 mode %s\t%d\r\n", __func__,
|
|
__LINE__);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* 2. Enable High speed mode */
|
|
mmc_ret = sdrv_mmc_set_hs_interface(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error adjusting interface speed!:%s\t%d\r\n",
|
|
__func__,
|
|
__LINE__);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*3. Enable DDR mode */
|
|
mmc_ret = sdrv_mmc_set_ddr_mode(host, card, width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure setting DDR mode:%s\t%d\r\n", __func__,
|
|
__LINE__);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*4. Set hs400 timing */
|
|
mmc_ret = sdrv_mmc_switch_cmd(host, card, MMC_ACCESS_WRITE,
|
|
MMC_EXT_MMC_HS_TIMING, MMC_HS400_TIMING);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Switch cmd returned failure %s\t%d\r\n", __func__,
|
|
__LINE__);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Enable HS400 mode in controller */
|
|
/* Save the timing value, before changing the clock */
|
|
MMC_SAVE_TIMING(host, SDHCI_EMMC_HS400_MODE);
|
|
|
|
/* Set the clock back to 200 MHZ */
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, MMC_CLK_200MHZ);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
}
|
|
|
|
if (host->ops->set_uhs_mode)
|
|
host->ops->set_uhs_mode(host, SDHCI_EMMC_HS400_MODE);
|
|
|
|
DETAIL_PRINTF(SSDK_DEBUG, "\r\n Enabling HS400 Mode Done\r\n");
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
status_t sdrv_mmc_send_tuning(struct mmc_device *dev, uint32_t opcode,
|
|
uint32_t bus_width)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd;
|
|
const uint8_t *tuning_block_pattern = NULL;
|
|
int size;
|
|
|
|
/* Judgment tuning data */
|
|
if (opcode == CMD21_SEND_TUNING_BLOCK &&
|
|
bus_width == MMC_DATA_BUS_WIDTH_8BIT) {
|
|
tuning_block_pattern = tuning_blk_pattern_8bit;
|
|
size = sizeof(tuning_blk_pattern_8bit);
|
|
}
|
|
else if (bus_width == MMC_DATA_BUS_WIDTH_4BIT) {
|
|
tuning_block_pattern = tuning_blk_pattern_4bit;
|
|
size = sizeof(tuning_blk_pattern_4bit);
|
|
}
|
|
else
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
|
|
/* Configuration commands */
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
cmd.cmd_index = opcode;
|
|
cmd.argument = 0x0;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.data_present = 0x1;
|
|
/* Configuration data */
|
|
cmd.data.data_ptr = tuning_data;
|
|
cmd.data.num_blocks = 0x1;
|
|
cmd.data.blk_sz = size;
|
|
memset(tuning_data, 0, size);
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
udelay(100);
|
|
DETAIL_PRINTF(SSDK_INFO, "sdrv_mmc_send_tuning send error!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Bad judgment */
|
|
if (cmd.error) {
|
|
udelay(100);
|
|
DETAIL_PRINTF(SSDK_INFO, "sdrv_mmc_send_tuning cmd error! reg = 0x%08x\r\n",
|
|
cmd.error);
|
|
return SDRV_STATUS_MMC_CMD_ERR;
|
|
}
|
|
|
|
if (cmd.data_present && dev->host.data_cmd.error) {
|
|
udelay(100);
|
|
DETAIL_PRINTF(SSDK_INFO, "sdrv_mmc_send_tuning data error! 0x%08x\r\n",
|
|
dev->host.data_cmd.error);
|
|
return SDRV_STATUS_MMC_DAT_ERR;
|
|
}
|
|
|
|
/* Data comparison */
|
|
if (memcmp(tuning_data, tuning_block_pattern, size)) {
|
|
udelay(100);
|
|
DETAIL_PRINTF(SSDK_INFO, "memcmp tuning_data error\r\n");
|
|
return SDRV_STATUS_MMC_TUNING_FAIL;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: sdrv_mmc_host_init
|
|
* Arg : mmc device structure
|
|
* Return : 0 on success, 1 on Failure
|
|
* Flow : Initialize the host contoller
|
|
* Set the clock rate to 400 KHZ for init
|
|
*/
|
|
static status_t sdrv_mmc_host_init(struct mmc_device *dev)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
struct sdhci_host *host;
|
|
struct mmc_config_data *cfg;
|
|
|
|
host = &dev->host;
|
|
cfg = &dev->config;
|
|
|
|
host->base = cfg->sdhc_base;
|
|
host->slot = cfg->slot;
|
|
host->irq = cfg->irq;
|
|
|
|
host->caps.hs200_support = cfg->hs200_support;
|
|
host->caps.hs400_support = cfg->hs400_support;
|
|
host->caps.uhs_support = cfg->uhs_support;
|
|
host->caps.hw_reset_support = cfg->hw_reset_support;
|
|
host->max_clk_rate = cfg->max_clk_rate;
|
|
|
|
if (cfg->voltage)
|
|
host->caps.voltage = cfg->voltage;
|
|
|
|
/*
|
|
* Initialize the controller, read the host capabilities
|
|
* set power on mode
|
|
*/
|
|
sdrv_sdhci_init(host);
|
|
|
|
host->caps.ddr_support &= cfg->ddr50_support;
|
|
|
|
if (host->ops->config_pin)
|
|
host->ops->config_pin(host);
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc identify card
|
|
* Arg : host & card structure
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Performs card identification process:
|
|
* 1. Get card's unique identification number (CID)
|
|
* 2. Get(for sd)/set (for mmc) relative card address (RCA)
|
|
* 3. Select the card to put it in TRAN state
|
|
*/
|
|
static status_t sdrv_mmc_identify_card(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
/* Ask card to send its unique card identification (CID) number (CMD2) */
|
|
mmc_ret = sdrv_mmc_all_send_cid(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure getting card's CID number!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Ask card to send a relative card address (RCA) (CMD3) */
|
|
mmc_ret = sdrv_mmc_send_relative_address(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure getting card's RCA!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Get card's CSD register (CMD9) */
|
|
mmc_ret = sdrv_mmc_send_csd(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure getting card's CSD information!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Select the card (CMD7) */
|
|
mmc_ret = sdrv_mmc_select_card(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure selecting the Card with RCA: %x\r\n",
|
|
card->rca);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Set the card status as active */
|
|
card->status = MMC_STATUS_ACTIVE;
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: sdrv_mmc_reset_card_and_send_op
|
|
* Arg : Host & Card structure
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Routine to initialize MMC card. It resets a card to idle state,
|
|
* verify operating voltage and set the card in ready state.
|
|
*/
|
|
static status_t sdrv_mmc_reset_card_and_send_op(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
/* 1. Card Reset - CMD0, for card powerup need some delay */
|
|
udelay(1000);
|
|
mmc_ret = sdrv_mmc_reset_card(host);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure resetting MMC cards!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* 2. Card Initialization process */
|
|
/*
|
|
* Send CMD1 to identify and reject cards that do not match host's VDD range
|
|
* profile. Cards sends its OCR register in response.
|
|
*/
|
|
mmc_ret = sdrv_mmc_send_op_cond(host, card);
|
|
|
|
/* OCR is not received, init could not complete */
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_ERR, "Failure getting OCR response from MMC Card\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
static status_t sdrv_mmc_send_app_cmd(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd = {0};
|
|
|
|
cmd.cmd_index = CMD55_APP_CMD;
|
|
cmd.argument = (card->rca << 16);
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed Sending CMD55\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
static status_t sdrv_mmc_switch_uhs_voltage(struct sdhci_host *host)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd = {0};
|
|
|
|
cmd.cmd_index = CMD11_SWITCH_VOLTAGE;
|
|
cmd.argument = 0;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "The CMD11 no response\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
udelay(1000);
|
|
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, 0);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
}
|
|
|
|
host->caps.voltage = SDHCI_VOL_1_8;
|
|
host->ops->set_power(host);
|
|
|
|
udelay(10000);
|
|
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, SDHCI_CLK_25MHZ);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
}
|
|
|
|
udelay(1000);
|
|
return mmc_ret;
|
|
}
|
|
|
|
status_t sdrv_mmc_sd_card_init(struct sdhci_host *host, struct mmc_card *card)
|
|
{
|
|
uint8_t i;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* Use the SD card RCA 0x0 during init */
|
|
card->rca = SD_CARD_RCA;
|
|
|
|
/* Send CMD8 for voltage check*/
|
|
for (i = 0; i < SD_CMD8_MAX_RETRY; i++) {
|
|
cmd.cmd_index = CMD8_SEND_IF_COND;
|
|
cmd.argument = MMC_SD_HC_VOLT_SUPPLIED;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R7;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"The response for CMD8 does not match the supplied value\r\n");
|
|
return mmc_ret;
|
|
}
|
|
else {
|
|
/* If the command response echos the voltage back */
|
|
if (cmd.resp[0] == MMC_SD_HC_VOLT_SUPPLIED)
|
|
break;
|
|
}
|
|
|
|
udelay(1);
|
|
}
|
|
|
|
if (i == SD_CMD8_MAX_RETRY && (cmd.resp[0] != MMC_SD_HC_VOLT_SUPPLIED)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error: CMD8 response timed out\r\n");
|
|
return SDRV_STATUS_TIMEOUT;
|
|
}
|
|
|
|
/* Send ACMD41 for OCR */
|
|
for (i = 0; i < SD_ACMD41_MAX_RETRY; i++) {
|
|
/* Send APP_CMD before ACMD41*/
|
|
mmc_ret = sdrv_mmc_send_app_cmd(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed sending App command\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* APP_CMD is successful, send ACMD41 now */
|
|
cmd.cmd_index = ACMD41_SEND_OP_COND;
|
|
cmd.argument = MMC_SD_OCR | MMC_SD_HC_HCS | MMC_SD_S18R;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R3;
|
|
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure sending ACMD41\r\n");
|
|
return mmc_ret;
|
|
}
|
|
else {
|
|
if (cmd.resp[0] & MMC_SD_DEV_READY) {
|
|
if (cmd.resp[0] & (1 << 30)) {
|
|
if (host->caps.uhs_support && cmd.resp[0] & (1 << 24))
|
|
card->type = MMC_CARD_TYPE_UHS;
|
|
else
|
|
card->type = MMC_CARD_TYPE_SDHC;
|
|
}
|
|
else {
|
|
card->type = MMC_CARD_TYPE_STD_SD;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
udelay(50000);
|
|
}
|
|
|
|
if (i == SD_ACMD41_MAX_RETRY && !(cmd.resp[0] & MMC_SD_DEV_READY)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error: ACMD41 response timed out\r\n");
|
|
return SDRV_STATUS_TIMEOUT;
|
|
}
|
|
|
|
if (card->type == MMC_CARD_TYPE_UHS) {
|
|
mmc_ret = sdrv_mmc_switch_uhs_voltage(host);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error: switch voltage to uhs mode failed\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Function to read SD card information from SD status */
|
|
static status_t sdrv_mmc_sd_get_card_ssr(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
static uint8_t raw_sd_status[ROUNDUP(64,
|
|
CONFIG_ARCH_CACHE_LINE)] __ALIGNED(CONFIG_ARCH_CACHE_LINE);
|
|
struct mmc_command cmd = {0};
|
|
uint32_t sd_status[16];
|
|
uint32_t *status = sd_status;
|
|
uint32_t au_size;
|
|
int i;
|
|
int j;
|
|
|
|
mmc_ret = sdrv_mmc_send_app_cmd(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed sending App command\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
cmd.cmd_index = ACMD13_SEND_SD_STATUS;
|
|
cmd.argument = 0x0;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.data_present = 0x1;
|
|
cmd.data.data_ptr = raw_sd_status;
|
|
cmd.data.num_blocks = 0x1;
|
|
cmd.data.blk_sz = 0x40;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
memcpy(sd_status, raw_sd_status, sizeof(sd_status));
|
|
|
|
for (i = 15, j = 0; i >= 0; i--, j++)
|
|
sd_status[i] = swap_endian32(sd_status[j]);
|
|
|
|
au_size = UNPACK_BITS(status, MMC_SD_AU_SIZE_BIT, MMC_SD_AU_SIZE_LEN, 32);
|
|
/* Card AU size in sectors */
|
|
card->ssr.au_size = 1 << (au_size + 4);
|
|
card->ssr.num_aus =
|
|
UNPACK_BITS(status, MMC_SD_ERASE_SIZE_BIT, MMC_SD_ERASE_SIZE_LEN, 32);
|
|
/* When Erase Time-out Calculation is not supported. set the num_aus 1 */
|
|
card->ssr.num_aus = MAX(card->ssr.num_aus, 1);
|
|
card->ssr.erase_timeout =
|
|
UNPACK_BITS(status, MMC_SD_ERASE_TOUT_BIT, MMC_SD_ERASE_TOUT_LEN, 32);
|
|
card->ssr.erase_offset = UNPACK_BITS(status, MMC_SD_ERASE_OFFSET_BIT,
|
|
MMC_SD_ERASE_OFFSET_LEN, 32);
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Function to read the SD CARD configuration register */
|
|
static uint32_t sdrv_mmc_sd_get_card_scr(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
static uint8_t scr_resp[ROUNDUP(8,
|
|
CONFIG_ARCH_CACHE_LINE)] __ALIGNED(CONFIG_ARCH_CACHE_LINE);
|
|
struct mmc_command cmd = {0};
|
|
uint32_t raw_scr[2];
|
|
|
|
/* Now read the SCR register */
|
|
/* Send APP_CMD before ACMD51*/
|
|
mmc_ret = sdrv_mmc_send_app_cmd(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed sending App command\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
cmd.cmd_index = ACMD51_READ_CARD_SCR;
|
|
cmd.argument = 0x0;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.data_present = 0x1;
|
|
cmd.data.data_ptr = scr_resp;
|
|
cmd.data.num_blocks = 0x1;
|
|
cmd.data.blk_sz = 0x8;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
memcpy(raw_scr, scr_resp, sizeof(raw_scr));
|
|
|
|
card->raw_scr[0] = swap_endian32(raw_scr[0]);
|
|
card->raw_scr[1] = swap_endian32(raw_scr[1]);
|
|
|
|
/*
|
|
* Parse & Populate the SCR data as per sdcc spec
|
|
*/
|
|
card->scr.bus_widths =
|
|
(card->raw_scr[0] & SD_SCR_BUS_WIDTH_MASK) >> SD_SCR_BUS_WIDTH;
|
|
card->scr.cmd23_support = (card->raw_scr[0] & SD_SCR_CMD23_SUPPORT);
|
|
card->scr.sd_spec =
|
|
(card->raw_scr[0] & SD_SCR_SD_SPEC_MASK) >> SD_SCR_SD_SPEC;
|
|
card->scr.sd3_spec =
|
|
(card->raw_scr[0] & SD_SCR_SD_SPEC3_MASK) >> SD_SCR_SD_SPEC3;
|
|
card->scr.data_stat_after_erase =
|
|
(card->raw_scr[0] & SD_SCR_STAT_AFTER_ERASE_MASK) >> SD_SCR_SD_STAT_AFTER_ERASE;
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc_set_sd_bus_width
|
|
* Arg : host, device structure & width
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Set the bus width for the card
|
|
*/
|
|
status_t sdrv_mmc_sd_set_bus_width(struct sdhci_host *host,
|
|
struct mmc_card *card,
|
|
uint8_t width)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd = {0};
|
|
|
|
/* Send APP_CMD before ACMD6*/
|
|
mmc_ret = sdrv_mmc_send_app_cmd(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed sending App command\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
cmd.cmd_index = ACMD6_SET_BUS_WIDTH;
|
|
cmd.argument = (width == MMC_DATA_BUS_WIDTH_4BIT) ? (1 << 1) : 0;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
status_t sdrv_mmc_sd_set_hs(struct sdhci_host *host, struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd = {0};
|
|
static uint8_t switch_resp[ROUNDUP(64,
|
|
CONFIG_ARCH_CACHE_LINE)] __ALIGNED(CONFIG_ARCH_CACHE_LINE);
|
|
cmd.cmd_index = CMD6_SWITCH_FUNC;
|
|
cmd.argument = MMC_SD_SWITCH_HS;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.data_present = 0x1;
|
|
cmd.data.data_ptr = switch_resp;
|
|
cmd.data.num_blocks = 0x1;
|
|
cmd.data.blk_sz = 0x40;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
MMC_SAVE_TIMING(host, SDHCI_UHS_SDR25_MODE);
|
|
|
|
/* Set the SDR25 mode in controller*/
|
|
if (host->ops->set_uhs_mode)
|
|
host->ops->set_uhs_mode(host, SDHCI_UHS_SDR25_MODE);
|
|
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, SDHCI_CLK_50MHZ);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
status_t sdrv_mmc_sd_switch(struct sdhci_host *host, struct mmc_card *card,
|
|
int mode,
|
|
int group, uint8_t value, uint8_t *resp)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd = {0};
|
|
cmd.cmd_index = CMD6_SWITCH_FUNC;
|
|
cmd.argument = (mode << 31) | 0xFFFFFF;
|
|
cmd.argument &= ~(0xF << (4 * (group - 1)));
|
|
cmd.argument |= (value << (4 * (group - 1)));
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.data_present = 0x1;
|
|
cmd.data.data_ptr = resp;
|
|
cmd.data.num_blocks = 0x1;
|
|
cmd.data.blk_sz = 0x40;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
|
|
status_t sdrv_mmc_sd_init_uhs_sdr25(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
static uint8_t switch_resp[ROUNDUP(64,
|
|
CONFIG_ARCH_CACHE_LINE)] __ALIGNED(CONFIG_ARCH_CACHE_LINE);
|
|
|
|
/* set max current 200mA */
|
|
mmc_ret = sdrv_mmc_sd_switch(host, card, 1, 4, 0, switch_resp);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
mmc_ret = sdrv_mmc_sd_switch(host, card, 1, 1, 1, switch_resp);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
MMC_SAVE_TIMING(host, SDHCI_UHS_SDR25_MODE);
|
|
|
|
/* Set the SDR25 mode in controller*/
|
|
if (host->ops->set_uhs_mode)
|
|
host->ops->set_uhs_mode(host, SDHCI_UHS_SDR25_MODE);
|
|
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, SDHCI_CLK_50MHZ);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
uint32_t sdrv_mmc_sd_init_uhs_ddr50(struct sdhci_host *host,
|
|
struct mmc_card *card)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
static uint8_t switch_resp[ROUNDUP(64,
|
|
CONFIG_ARCH_CACHE_LINE)] __ALIGNED(CONFIG_ARCH_CACHE_LINE);
|
|
|
|
/* set max current 400mA */
|
|
mmc_ret = sdrv_mmc_sd_switch(host, card, 1, 4, 1, switch_resp);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
mmc_ret = sdrv_mmc_sd_switch(host, card, 1, 1, 4, switch_resp);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
MMC_SAVE_TIMING(host, SDHCI_UHS_DDR50_MODE);
|
|
|
|
/* Set the DDR50 mode in controller*/
|
|
if (host->ops->set_uhs_mode)
|
|
host->ops->set_uhs_mode(host, SDHCI_UHS_DDR50_MODE);
|
|
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, SDHCI_CLK_50MHZ);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc_init_card
|
|
* Arg : mmc device structure
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Performs initialization and identification of eMMC cards connected
|
|
* to the host.
|
|
*/
|
|
|
|
static status_t sdrv_mmc_card_init(struct mmc_device *dev)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
uint8_t bus_width = 0;
|
|
|
|
struct sdhci_host *host;
|
|
struct mmc_card *card;
|
|
struct mmc_config_data *cfg;
|
|
|
|
host = &dev->host;
|
|
card = &dev->card;
|
|
cfg = &dev->config;
|
|
|
|
host->card_type = SDHCI_EMMC_CARD;
|
|
|
|
/* card hw reset */
|
|
if ((host->caps.hw_reset_support) && (host->ops->hw_reset))
|
|
host->ops->hw_reset(host);
|
|
|
|
/* host init */
|
|
if (host->ops->priv_init)
|
|
host->ops->priv_init(host);
|
|
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, SDHCI_CLK_400KHZ);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set card clk\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
|
|
/* Initialize MMC card structure */
|
|
card->status = MMC_STATUS_INACTIVE;
|
|
|
|
/* If config 1.8v voltage, set ocr 1.7v-1.9v params, else set 2.7v-3.6v */
|
|
card->ocr = MMC_OCR_27_36 | MMC_OCR_SEC_MODE;
|
|
|
|
if (host->caps.voltage == SDHCI_VOL_1_8)
|
|
card->ocr = MMC_OCR_17_19 | MMC_OCR_SEC_MODE;
|
|
|
|
/* Initialize the internal MMC */
|
|
mmc_ret = sdrv_mmc_reset_card_and_send_op(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_ERR, "MMC card failed to respond, try for SD card\r\n");
|
|
host->card_type = SDHCI_SD_CARD;
|
|
|
|
/* host init */
|
|
if (host->ops->priv_init)
|
|
host->ops->priv_init(host);
|
|
|
|
/* need reinit clock */
|
|
if (host->ops->set_clock) {
|
|
mmc_ret = host->ops->set_clock(host, SDHCI_CLK_400KHZ);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set card clk\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
|
|
/* Reset the card & get the OCR */
|
|
mmc_ret = sdrv_mmc_sd_card_init(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to initialize SD card\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
|
|
/* Identify (CMD2, CMD3 & CMD9) and select the card (CMD7) */
|
|
mmc_ret = sdrv_mmc_identify_card(host, card);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
/* set interface speed */
|
|
if (MMC_CARD_SD(card)) {
|
|
if (card->type == MMC_CARD_TYPE_UHS) {
|
|
if (cfg->bus_width == MMC_BUS_WIDTH_4BIT) {
|
|
/* uhs card only support 4bit bus width */
|
|
bus_width = MMC_DATA_BUS_WIDTH_4BIT;
|
|
}
|
|
else {
|
|
DETAIL_PRINTF(SSDK_CRIT, "uhs card only support 4bit bus widt\r\n");
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
mmc_ret = sdrv_mmc_sd_set_bus_width(host, card, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set bus width for the card\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Set bit bus width in controller */
|
|
mmc_ret = sdrv_sdhci_set_bus_width(host, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set bus width for host controller\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
if (host->caps.ddr_support) {
|
|
DETAIL_PRINTF(SSDK_INFO, "SDHC Running in DDR mode\r\n");
|
|
mmc_ret = sdrv_mmc_sd_init_uhs_ddr50(host, card);
|
|
}
|
|
else {
|
|
DETAIL_PRINTF(SSDK_INFO, "SDHC Running in SDR mode\r\n");
|
|
mmc_ret = sdrv_mmc_sd_init_uhs_sdr25(host, card);
|
|
}
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set SDR or DDR for SD card\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
}
|
|
else {
|
|
DETAIL_PRINTF(SSDK_INFO, "SDHC Running in HS mode\r\n");
|
|
mmc_ret = sdrv_mmc_sd_set_hs(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set HS for SD card\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
mmc_ret = sdrv_mmc_set_hs_interface(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error adjusting interface speed!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
}
|
|
|
|
/* Now get the extended CSD for the card */
|
|
if (MMC_CARD_MMC(card)) {
|
|
/* For MMC cards, also get the extended csd */
|
|
mmc_ret = sdrv_mmc_get_ext_csd(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure getting card's ExtCSD information!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
else {
|
|
/*Read SCR for sd card */
|
|
mmc_ret = sdrv_mmc_sd_get_card_scr(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure getting card's SCR register\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Read SSR for the SD card */
|
|
mmc_ret = sdrv_mmc_sd_get_card_ssr(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to get SSR from the card\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
|
|
/* Decode and save the CSD register */
|
|
mmc_ret = sdrv_mmc_decode_and_save_csd(card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure decoding card's CSD information!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
if (MMC_CARD_MMC(card)) {
|
|
/* Set the bus width based on host, target capbilities */
|
|
if (cfg->bus_width == MMC_BUS_WIDTH_8BIT && host->caps.bus_width_8bit)
|
|
bus_width = MMC_DATA_BUS_WIDTH_8BIT;
|
|
/*
|
|
* Host contoller by default supports 4 bit & 1 bit mode.
|
|
* No need to check for host support here
|
|
*/
|
|
else if (cfg->bus_width == MMC_BUS_WIDTH_4BIT)
|
|
bus_width = MMC_DATA_BUS_WIDTH_4BIT;
|
|
else
|
|
bus_width = MMC_DATA_BUS_WIDTH_1BIT;
|
|
|
|
/* Set 4/8 bit SDR bus width in controller */
|
|
mmc_ret = sdrv_sdhci_set_bus_width(host, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set bus width for host controller\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Enable high speed mode in the follwing order:
|
|
* 1. HS400 mode if supported by host & card
|
|
* 1. HS200 mode if supported by host & card
|
|
* 2. DDR mode host, if supported by host & card
|
|
* 3. Use normal speed mode with supported bus width
|
|
*/
|
|
if (host->caps.hs400_support && sdrv_mmc_card_supports_hs400_mode(card)) {
|
|
DETAIL_PRINTF(SSDK_INFO, "SDHC Running in HS400 mode\r\n");
|
|
mmc_ret = sdrv_mmc_set_hs400_mode(host, card, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure to set HS400 mode for Card(RCA:%x)\r\n",
|
|
card->rca);
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
|
|
#if USE_TARGET_HS200_CAPS
|
|
else if (host->caps.hs200_support && host->caps.sdr104_support &&
|
|
sdrv_mmc_card_supports_hs200_mode(card))
|
|
#else
|
|
else if (host->caps.sdr104_support &&
|
|
sdrv_mmc_card_supports_hs200_mode(card))
|
|
#endif
|
|
{
|
|
DETAIL_PRINTF(SSDK_INFO, "SDHC Running in HS200 mode\r\n");
|
|
mmc_ret = sdrv_mmc_set_hs200_mode(host, card, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure to set HS200 mode for Card(RCA:%x)\r\n",
|
|
card->rca);
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
else if (host->caps.ddr_support && sdrv_mmc_card_supports_ddr_mode(card)) {
|
|
DETAIL_PRINTF(SSDK_INFO, "SDHC Running in DDR mode\r\n");
|
|
mmc_ret = sdrv_mmc_set_ddr_mode(host, card, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure to set DDR mode for Card(RCA:%x)\r\n",
|
|
card->rca);
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
else {
|
|
DETAIL_PRINTF(SSDK_INFO, "SDHC Running in High Speed mode\r\n");
|
|
/* Set HS_TIMING mode */
|
|
mmc_ret = sdrv_mmc_set_hs_interface(host, card);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure to enalbe HS mode for Card(RCA:%x)\r\n",
|
|
card->rca);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Set wide bus mode */
|
|
mmc_ret = sdrv_mmc_set_bus_width(host, card, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failure to set wide bus for Card(RCA:%x)\r\n",
|
|
card->rca);
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
}
|
|
else if (card->type != MMC_CARD_TYPE_UHS) {
|
|
/* Check the supported bus width for the card from SCR register */
|
|
if ((card->scr.bus_widths & SD_SCR_WIDTH_4BIT)
|
|
&& (cfg->bus_width == MMC_BUS_WIDTH_4BIT))
|
|
bus_width = MMC_DATA_BUS_WIDTH_4BIT;
|
|
else
|
|
bus_width = MMC_DATA_BUS_WIDTH_1BIT;
|
|
|
|
mmc_ret = sdrv_mmc_sd_set_bus_width(host, card, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set bus width for the card\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Set bit SDR bus width in controller */
|
|
mmc_ret = sdrv_sdhci_set_bus_width(host, bus_width);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set bus width for host controller\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
|
|
card->block_size = MMC_BLK_SZ;
|
|
|
|
#ifdef MMC_ENABLE_RST_N_FUNC
|
|
|
|
if (MMC_CARD_MMC(card)) {
|
|
/* Enable RST_n_FUNCTION */
|
|
if (!card->ext_csd[MMC_EXT_CSD_RST_N_FUNC]) {
|
|
mmc_ret =
|
|
sdrv_mmc_switch_cmd(host, card, MMC_SET_BIT, MMC_EXT_CSD_RST_N_FUNC,
|
|
RST_N_FUNC_ENABLE);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to enable RST_n_FUNCTION\r\n");
|
|
return mmc_ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
if (MMC_CARD_MMC(card) && dev->card.ext_csd[MMC_EXT_CSD_REV] >= 6) {
|
|
mmc_ret = sdrv_mmc_switch_cmd(&dev->host, &dev->card, MMC_ACCESS_WRITE,
|
|
MMC_POWER_OFF_NOTIFICATION, MMC_POWER_ON);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to power off notification MMC_POWER_ON\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
card->ext_csd[MMC_POWER_OFF_NOTIFICATION] = MMC_POWER_ON;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc display csd
|
|
* Arg : None
|
|
* Return : None
|
|
* Flow : Displays the csd information
|
|
*/
|
|
static void sdrv_mmc_display_csd(struct mmc_card *card)
|
|
{
|
|
ssdk_printf(SSDK_INFO, "erase_grpsize: %d\r\n", card->csd.erase_grp_size);
|
|
ssdk_printf(SSDK_INFO, "erase_grpmult: %d\r\n", card->csd.erase_grp_mult);
|
|
ssdk_printf(SSDK_INFO, "wp_grpsize: %d\r\n", card->csd.wp_grp_size);
|
|
ssdk_printf(SSDK_INFO, "wp_grpen: %d\r\n", card->csd.wp_grp_enable);
|
|
ssdk_printf(SSDK_INFO, "perm_wp: %d\r\n", card->csd.perm_wp);
|
|
ssdk_printf(SSDK_INFO, "temp_wp: %d\r\n", card->csd.temp_wp);
|
|
}
|
|
|
|
uint32_t sdrv_mmc_get_erase_group_size(struct mmc_device *dev)
|
|
{
|
|
struct mmc_csd *mmc_csd = &dev->card.csd;
|
|
|
|
if (dev->card.type < MMC_TYPE_MMCHC) {
|
|
DETAIL_PRINTF(SSDK_INFO, "\r\n sd card au_size %d, num_aus %d\r\n",
|
|
dev->card.ssr.au_size, dev->card.ssr.num_aus);
|
|
|
|
if (mmc_csd->cmmc_structure == 1) {
|
|
/* CSD Version 2.0 */
|
|
dev->erase_grp_size = MMC_BLK_SZ;
|
|
}
|
|
else {
|
|
/* CSD Version 1.0 */
|
|
if (mmc_csd->erase_blk_len)
|
|
dev->erase_grp_size = MMC_BLK_SZ;
|
|
else
|
|
dev->erase_grp_size = mmc_csd->sector_size * MMC_BLK_SZ;
|
|
}
|
|
}
|
|
else {
|
|
DETAIL_PRINTF(SSDK_INFO, "\r\n mmc card erase_size %d, erase_mult %d\r\n",
|
|
dev->card.csd.erase_grp_size, dev->card.csd.erase_grp_mult);
|
|
dev->erase_grp_size = (dev->card.csd.erase_grp_size + 1) *
|
|
(dev->card.csd.erase_grp_mult + 1) * MMC_BLK_SZ;
|
|
}
|
|
|
|
return dev->erase_grp_size;
|
|
}
|
|
|
|
uint64_t sdrv_mmc_get_size(struct mmc_device *dev, uint32_t type)
|
|
{
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
if (MMC_CARD_MMC(card)) {
|
|
switch (type) {
|
|
case PART_ACCESS_DEFAULT:
|
|
return card->capacity;
|
|
break;
|
|
|
|
case PART_ACCESS_BOOT1:
|
|
return card->boot_size;
|
|
break;
|
|
|
|
case PART_ACCESS_BOOT2:
|
|
return card->boot_size;
|
|
break;
|
|
|
|
case PART_ACCESS_RPMB:
|
|
return card->rpmb_size;
|
|
break;
|
|
|
|
case PART_ACCESS_GP1:
|
|
return 0;
|
|
break;
|
|
|
|
case PART_ACCESS_GP2:
|
|
return 0;
|
|
break;
|
|
|
|
case PART_ACCESS_GP3:
|
|
return 0;
|
|
break;
|
|
|
|
case PART_ACCESS_GP4:
|
|
return 0;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
else if (MMC_CARD_SD(card)) {
|
|
switch (type) {
|
|
case PART_ACCESS_DEFAULT:
|
|
return card->capacity;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
uint32_t sdrv_mmc_get_block_size(struct mmc_device *dev)
|
|
{
|
|
struct mmc_card *card = &dev->card;
|
|
return card->block_size;
|
|
}
|
|
|
|
uint32_t sdrv_mmc_get_data_after_erase(struct mmc_device *dev)
|
|
{
|
|
struct mmc_card *card = &dev->card;
|
|
return MMC_CARD_SD(card) ? card->scr.data_stat_after_erase : 0;
|
|
}
|
|
|
|
/*
|
|
* Function: sdrv_mmc_alloc_dev
|
|
* Arg : None
|
|
* Return : Pointer to mmc device
|
|
* Flow : Alloc mmc device memory
|
|
*/
|
|
struct mmc_device *sdrv_mmc_alloc_dev(void)
|
|
{
|
|
struct mmc_device *dev;
|
|
|
|
dev = (struct mmc_device *)malloc(sizeof(struct mmc_device));
|
|
|
|
if (!dev) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error allocating mmc device\r\n");
|
|
return NULL;
|
|
}
|
|
|
|
memset(dev, 0, sizeof(struct mmc_device));
|
|
return dev;
|
|
}
|
|
|
|
/*
|
|
* Function: sdrv_mmc_sdrv_sdhci_init
|
|
* Arg : mmc device structure
|
|
* MMC configuration data
|
|
* Return : 0 on Success, 1 on Failure
|
|
* Flow : Entry point to MMC boot process
|
|
* Initialize the sd host controller
|
|
* Initialize the mmc card
|
|
* Set the clock & high speed mode
|
|
*/
|
|
status_t sdrv_mmc_sdrv_sdhci_init(struct mmc_device *dev)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
ASSERT(dev);
|
|
|
|
memset(&dev->card, 0, sizeof(struct mmc_card));
|
|
|
|
dev->part_curr = PART_ACCESS_DEFAULT;
|
|
|
|
/* Initialize the host & clock */
|
|
DETAIL_PRINTF(SSDK_INFO,
|
|
" Initializing MMC host data structure and clock!\r\n");
|
|
|
|
mmc_ret = sdrv_mmc_host_init(dev);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error Initializing MMC host : %u\r\n", mmc_ret);
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Initialize and identify cards connected to host */
|
|
mmc_ret = sdrv_mmc_card_init(dev);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed detecting MMC/SDC @ slot%d\r\n",
|
|
dev->config.slot);
|
|
return mmc_ret;
|
|
}
|
|
|
|
DETAIL_PRINTF(SSDK_INFO, "Done initialization of the card\r\n");
|
|
|
|
sdrv_mmc_display_csd(&dev->card);
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
static status_t sdrv_mmc_parse_response(uint32_t resp)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
/* Trying to write beyond card capacity */
|
|
if (resp & MMC_R1_ADDR_OUT_OF_RANGE) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Attempting to read or write beyond the Device capacity\r\n");
|
|
mmc_ret = SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
/* Misaligned address not matching block length */
|
|
if (resp & MMC_R1_ADDR_ERR) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"The misaligned address did not match the block length used\r\n");
|
|
mmc_ret = SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
/* Invalid block length */
|
|
if (resp & MMC_R1_BLOCK_LEN_ERR) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"The transferred bytes does not match the block length\r\n");
|
|
mmc_ret = SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
/* Tried to program write protected block */
|
|
if (resp & MMC_R1_WP_VIOLATION) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Attempt to program a write protected block\r\n");
|
|
mmc_ret = SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
/* card controller error */
|
|
if (resp & MMC_R1_CC_ERROR) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Device error occurred, which is not related to the host command\r\n");
|
|
mmc_ret = SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
/* Generic error */
|
|
if (resp & MMC_R1_GENERIC_ERR) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "A generic Device error\r\n");
|
|
mmc_ret = SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
/* Finally check for card in TRAN state */
|
|
if (MMC_CARD_STATUS(resp) != MMC_TRAN_STATE) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "MMC card is not in TRAN state\r\n");
|
|
mmc_ret = SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
static status_t sdrv_mmc_stop_command(struct mmc_device *dev)
|
|
{
|
|
struct mmc_command cmd;
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
if (dev->host.card_type == SDHCI_SDIO_CARD)
|
|
cmd.cmd_index = CMD52_SDIO_STOP_TRANSMISSION;
|
|
else
|
|
cmd.cmd_index = CMD12_STOP_TRANSMISSION;
|
|
|
|
cmd.argument = (dev->card.rca << 16) | BIT(0);
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_ABORT;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to send stop command\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Response contains 32 bit Card status.
|
|
* Parse the errors & provide relevant information */
|
|
|
|
return sdrv_mmc_parse_response(cmd.resp[0]);
|
|
}
|
|
|
|
static uint32_t sdrv_mmc_set_data_timeout(struct mmc_device *dev,
|
|
struct mmc_command *cmd)
|
|
{
|
|
struct sdhci_host *host = &dev->host;
|
|
struct mmc_card *card = &dev->card;
|
|
uint32_t mult = 1;
|
|
uint32_t timeout_ns;
|
|
uint32_t timeout_clks;
|
|
|
|
if (!cmd->data_present)
|
|
return 0;
|
|
|
|
/*
|
|
* SDIO cards only define an upper 1 s limit on access.
|
|
*/
|
|
if (MMC_CARD_SDIO(card))
|
|
cmd->cmd_timeout = 1000000;
|
|
|
|
/*
|
|
* SD cards use a 100 multiplier rather than 10
|
|
*/
|
|
mult = MMC_CARD_SD(card) ? 100 : 10;
|
|
|
|
/*
|
|
* Scale up the multiplier (and therefore the timeout) by
|
|
* the r2w factor for writes.
|
|
*/
|
|
if (cmd->trans_mode == SDHCI_MMC_WRITE)
|
|
mult <<= card->csd.r2w_factor;
|
|
|
|
timeout_ns = card->csd.taac_ns * mult;
|
|
timeout_clks = card->csd.taac_clks * mult;
|
|
|
|
/*
|
|
* SD cards also have an upper limit on the timeout.
|
|
*/
|
|
if (MMC_CARD_SD(card)) {
|
|
uint32_t timeout_us, limit_us;
|
|
|
|
timeout_us = timeout_ns / 1000;
|
|
|
|
if (host->cur_clk_rate)
|
|
timeout_us += timeout_clks * 1000 /
|
|
(host->cur_clk_rate / 1000);
|
|
|
|
if (cmd->trans_mode == SDHCI_MMC_WRITE)
|
|
/*
|
|
* The MMC spec "It is strongly recommended
|
|
* for hosts to implement more than 500ms
|
|
* timeout value even if the card indicates
|
|
* the 250ms maximum busy length." Even the
|
|
* previous value of 300ms is known to be
|
|
* insufficient for some cards.
|
|
*/
|
|
limit_us = 3000000;
|
|
else
|
|
limit_us = 100000;
|
|
|
|
/*
|
|
* SDHC cards always use these fixed values.
|
|
*/
|
|
if (timeout_us > limit_us) {
|
|
timeout_ns = limit_us * 1000;
|
|
timeout_clks = 0;
|
|
}
|
|
|
|
/* assign limit value if invalid */
|
|
if (timeout_us == 0)
|
|
timeout_ns = limit_us * 1000;
|
|
}
|
|
|
|
cmd->cmd_timeout = timeout_ns / 1000;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function: actual mmc sdhci read
|
|
* Arg : mmc device structure, block address, number of blocks & destination
|
|
* Return : 0 on Success, non zero on success
|
|
* Flow : Fill in the command structure & send the command
|
|
*/
|
|
static status_t sdrv_mmc_send_read(struct mmc_device *dev, void *dest,
|
|
uint32_t blk_addr,
|
|
uint32_t num_blocks)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd;
|
|
uint32_t status;
|
|
uint32_t retry = 0;
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD17/18 Format:
|
|
* [31:0] Data Address
|
|
*/
|
|
if (num_blocks == 1)
|
|
cmd.cmd_index = CMD17_READ_SINGLE_BLOCK;
|
|
else
|
|
cmd.cmd_index = CMD18_READ_MULTIPLE_BLOCK;
|
|
|
|
/*
|
|
* Standard emmc cards use byte mode addressing
|
|
* convert the block address to byte address before
|
|
* sending the command
|
|
*/
|
|
if (card->type == MMC_TYPE_STD_MMC)
|
|
cmd.argument = blk_addr * card->block_size;
|
|
else
|
|
cmd.argument = blk_addr;
|
|
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.data_present = 0x1;
|
|
|
|
/* Use CMD23 If card supports CMD23:
|
|
* For SD card use the value read from SCR register
|
|
* For emmc by default use CMD23.
|
|
* Also as per SDCC spec always use CMD23 to stop
|
|
* multiblock read/write if UHS (Ultra High Speed) is
|
|
* enabled
|
|
*/
|
|
if (MMC_CARD_SD(card))
|
|
cmd.cmd23_support = dev->card.scr.cmd23_support;
|
|
else
|
|
cmd.cmd23_support = 0x1;
|
|
|
|
cmd.data.data_ptr = dest;
|
|
cmd.data.num_blocks = num_blocks;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* config cmd data hw NAC timeout.
|
|
* delay between the end bit of the read command
|
|
* and the start bit of the data block.
|
|
* interrupt mode, hw timeout valid.
|
|
*/
|
|
sdrv_mmc_set_data_timeout(dev, &cmd);
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
/* For multi block read failures send stop command */
|
|
if (mmc_ret && num_blocks > 1) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to send read cmd\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Response contains 32 bit Card status.
|
|
* Parse the errors & provide relevant information
|
|
*/
|
|
mmc_ret = sdrv_mmc_parse_response(cmd.resp[0]);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
while (1) {
|
|
mmc_ret = sdrv_mmc_get_card_status(&dev->host, &dev->card, &status);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to get card status after read\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
if ((status & MMC_READY_FOR_DATA) &&
|
|
(MMC_CARD_STATUS(status) == MMC_TRAN_STATE))
|
|
break;
|
|
|
|
retry++;
|
|
udelay(1000);
|
|
|
|
if (retry == MMC_MAX_CARD_STAT_RETRY) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Card status check timed out after sending read command\r\n");
|
|
return SDRV_STATUS_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc sdhci read
|
|
* Arg : mmc device structure, block address, number of blocks & destination
|
|
* Return : 0 on Success, non zero on success
|
|
* Flow : Fill in the command structure & send the command
|
|
*/
|
|
status_t sdrv_mmc_sdhci_read(struct mmc_device *dev, void *dest,
|
|
uint32_t blk_addr,
|
|
uint32_t num_blocks)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct sdhci_host *host = &dev->host;
|
|
uint32_t desc_len_max = 0;
|
|
uint32_t blocks_num_max = 0;
|
|
uint32_t rblocks_num = 0;
|
|
uint64_t dest_offset = 0;
|
|
uint64_t addr_offset = 0;
|
|
|
|
if (host->caps.addr_64bit_v4) {
|
|
desc_len_max = SDHCI_ADMA2_DESC_LINE_SZ_V4 * (ADMA_DESC_LIST_SIZE - 1);
|
|
}
|
|
else {
|
|
desc_len_max = SDHCI_ADMA_DESC_LINE_SZ * (ADMA_DESC_LIST_SIZE - 1);
|
|
}
|
|
|
|
blocks_num_max = desc_len_max / SDHCI_MMC_BLK_SZ;
|
|
blocks_num_max = MIN(blocks_num_max, MMC_BLOCK_COUNT_MASK);
|
|
|
|
while (num_blocks) {
|
|
rblocks_num = MIN(blocks_num_max, num_blocks);
|
|
|
|
mmc_ret = sdrv_mmc_send_read(dev, (uint8_t *)dest + dest_offset,
|
|
blk_addr + addr_offset, rblocks_num);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
num_blocks -= rblocks_num;
|
|
addr_offset += rblocks_num;
|
|
dest_offset += rblocks_num * SDHCI_MMC_BLK_SZ;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function: actual mmc sdhci write
|
|
* Arg : mmc device structure, block address, number of blocks & source
|
|
* Return : 0 on Success, non zero on success
|
|
* Flow : Fill in the command structure & send the command
|
|
*/
|
|
static status_t sdrv_mmc_send_write(struct mmc_device *dev, void *src,
|
|
uint32_t blk_addr,
|
|
uint32_t num_blocks)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
uint32_t status;
|
|
uint32_t retry = 0;
|
|
struct mmc_command cmd;
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* CMD24/25 Format:
|
|
* [31:0] Data Address
|
|
*/
|
|
|
|
if (num_blocks == 1)
|
|
cmd.cmd_index = CMD24_WRITE_SINGLE_BLOCK;
|
|
else
|
|
cmd.cmd_index = CMD25_WRITE_MULTIPLE_BLOCK;
|
|
|
|
/*
|
|
* Standard emmc cards use byte mode addressing
|
|
* convert the block address to byte address before
|
|
* sending the command
|
|
*/
|
|
if (card->type == MMC_TYPE_STD_MMC)
|
|
cmd.argument = blk_addr * card->block_size;
|
|
else
|
|
cmd.argument = blk_addr;
|
|
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_WRITE;
|
|
|
|
/* Use CMD23 If card supports CMD23:
|
|
* For SD card use the value read from SCR register
|
|
* For emmc by default use CMD23.
|
|
* Also as per SDCC spec always use CMD23 to stop
|
|
* multiblock read/write if UHS (Ultra High Speed) is
|
|
* enabled
|
|
*/
|
|
if (MMC_CARD_SD(card))
|
|
cmd.cmd23_support = dev->card.scr.cmd23_support;
|
|
else
|
|
cmd.cmd23_support = 0x1;
|
|
|
|
cmd.data_present = 0x1;
|
|
cmd.data.data_ptr = src;
|
|
cmd.data.num_blocks = num_blocks;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* config cmd data hw NAC timeout.
|
|
* delay between the end bit of the read command
|
|
* and the start bit of the data block.
|
|
* interrupt mode, hw timeout valid.
|
|
*/
|
|
sdrv_mmc_set_data_timeout(dev, &cmd);
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
/* For multi block write failures send stop command */
|
|
if (mmc_ret && num_blocks > 1) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to send write cmd\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Response contains 32 bit Card status.
|
|
* Parse the errors & provide relevant information
|
|
*/
|
|
mmc_ret = sdrv_mmc_parse_response(cmd.resp[0]);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
while (1) {
|
|
mmc_ret = sdrv_mmc_get_card_status(&dev->host, &dev->card, &status);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to get card status after write\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
if ((status & MMC_READY_FOR_DATA) &&
|
|
(MMC_CARD_STATUS(status) == MMC_TRAN_STATE))
|
|
break;
|
|
|
|
retry++;
|
|
udelay(1000);
|
|
|
|
if (retry == MMC_MAX_CARD_STAT_RETRY) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Card status check timed out after sending write command\r\n");
|
|
return SDRV_STATUS_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc sdhci write
|
|
* Arg : mmc device structure, block address, number of blocks & source
|
|
* Return : 0 on Success, non zero on success
|
|
* Flow : Fill in the command structure & send the command
|
|
*/
|
|
status_t sdrv_mmc_sdhci_write(struct mmc_device *dev, void *src,
|
|
uint32_t blk_addr,
|
|
uint32_t num_blocks)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct sdhci_host *host = &dev->host;
|
|
uint32_t desc_len_max = 0;
|
|
uint32_t blocks_num_max = 0;
|
|
uint32_t rblocks_num = 0;
|
|
uint64_t src_offset = 0;
|
|
uint64_t addr_offset = 0;
|
|
|
|
if (host->caps.addr_64bit_v4) {
|
|
desc_len_max = SDHCI_ADMA2_DESC_LINE_SZ_V4 * (ADMA_DESC_LIST_SIZE - 1);
|
|
}
|
|
else {
|
|
desc_len_max = SDHCI_ADMA_DESC_LINE_SZ * (ADMA_DESC_LIST_SIZE - 1);
|
|
}
|
|
|
|
blocks_num_max = desc_len_max / SDHCI_MMC_BLK_SZ;
|
|
blocks_num_max = MIN(blocks_num_max, MMC_BLOCK_COUNT_MASK);
|
|
|
|
while (num_blocks) {
|
|
rblocks_num = MIN(blocks_num_max, num_blocks);
|
|
|
|
mmc_ret = sdrv_mmc_send_write(dev, (uint8_t *)src + src_offset,
|
|
blk_addr + addr_offset, rblocks_num);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
num_blocks -= rblocks_num;
|
|
addr_offset += rblocks_num;
|
|
src_offset += rblocks_num * SDHCI_MMC_BLK_SZ;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Send the erase group start address using CMD35
|
|
*/
|
|
static status_t sdrv_mmc_send_erase_grp_start(struct mmc_device *dev,
|
|
uint32_t erase_start)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd;
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
if (MMC_CARD_MMC(card))
|
|
cmd.cmd_index = CMD35_ERASE_GROUP_START;
|
|
else
|
|
cmd.cmd_index = CMD32_ERASE_WR_BLK_START;
|
|
|
|
/*
|
|
* Standard emmc cards use byte mode addressing
|
|
* convert the block address to byte address before
|
|
* sending the command
|
|
*/
|
|
if (card->type == MMC_TYPE_STD_MMC)
|
|
cmd.argument = erase_start * card->block_size;
|
|
else
|
|
cmd.argument = erase_start;
|
|
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
/*
|
|
* CMD35 on failure returns address out of range error
|
|
*/
|
|
if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0])) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Address for CMD35 is out of range\r\n");
|
|
return SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Send the erase group end address using CMD36
|
|
*/
|
|
static status_t sdrv_mmc_send_erase_grp_end(struct mmc_device *dev,
|
|
uint32_t erase_end)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd;
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
if (MMC_CARD_MMC(card))
|
|
cmd.cmd_index = CMD36_ERASE_GROUP_END;
|
|
else
|
|
cmd.cmd_index = CMD33_ERASE_WR_BLK_END;
|
|
|
|
/*
|
|
* Standard emmc cards use byte mode addressing
|
|
* convert the block address to byte address before
|
|
* sending the command
|
|
*/
|
|
if (card->type == MMC_TYPE_STD_MMC)
|
|
cmd.argument = erase_end * card->block_size;
|
|
else
|
|
cmd.argument = erase_end;
|
|
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
/*
|
|
* CMD3 on failure returns address out of range error
|
|
*/
|
|
if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0])) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Address for CMD36 is out of range\r\n");
|
|
return SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Send the erase CMD38, to erase the selected erase groups
|
|
*/
|
|
static status_t sdrv_mmc_send_erase(struct mmc_device *dev,
|
|
uint64_t erase_timeout)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd;
|
|
uint32_t status;
|
|
uint32_t retry = 0;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
cmd.cmd_index = CMD38_ERASE;
|
|
cmd.argument = 0x00000000;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1B;
|
|
cmd.cmd_timeout = erase_timeout;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
if (mmc_ret)
|
|
return mmc_ret;
|
|
|
|
do {
|
|
mmc_ret = sdrv_mmc_get_card_status(&dev->host, &dev->card, &status);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to get card status after erase\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Check if the response of erase command has eras skip status set */
|
|
if (status & MMC_R1_WP_ERASE_SKIP)
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Write Protect set for the region, only partial space was erased\r\n");
|
|
|
|
retry++;
|
|
udelay(1000);
|
|
|
|
if (retry == MMC_MAX_CARD_STAT_RETRY) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Card status check timed out after sending erase command\r\n");
|
|
return SDRV_STATUS_TIMEOUT;
|
|
}
|
|
}
|
|
while (!(status & MMC_READY_FOR_DATA) ||
|
|
(MMC_CARD_STATUS(status) == MMC_PROG_STATE));
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc sdhci erase
|
|
* Arg : mmc device structure, block address and length
|
|
* Return : 0 on Success, non zero on failure
|
|
* Flow : Fill in the command structure & send the command
|
|
*/
|
|
status_t sdrv_mmc_sdhci_erase(struct mmc_device *dev, uint32_t blk_addr,
|
|
uint64_t len)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
uint32_t erase_unit_sz = 0;
|
|
uint32_t erase_start;
|
|
uint32_t erase_end;
|
|
uint32_t blk_end;
|
|
uint32_t num_erase_grps;
|
|
uint64_t erase_timeout = 0;
|
|
struct mmc_card *card;
|
|
|
|
card = &dev->card;
|
|
|
|
/*
|
|
* Calculate the erase unit size,
|
|
* 1. Based on emmc 4.5 spec for emmc card
|
|
* 2. Use SD Card Status info for SD cards
|
|
*/
|
|
if (MMC_CARD_MMC(card)) {
|
|
/*
|
|
* Calculate the erase unit size as per the emmc specification v4.5
|
|
*/
|
|
if (dev->card.ext_csd[MMC_ERASE_GRP_DEF])
|
|
erase_unit_sz =
|
|
(MMC_HC_ERASE_MULT * dev->card.ext_csd[MMC_HC_ERASE_GRP_SIZE]) /
|
|
MMC_BLK_SZ;
|
|
else
|
|
erase_unit_sz = (dev->card.csd.erase_grp_size + 1) *
|
|
(dev->card.csd.erase_grp_mult + 1);
|
|
}
|
|
else {
|
|
struct mmc_csd *mmc_csd = &card->csd;
|
|
|
|
if (mmc_csd->cmmc_structure == 1) {
|
|
/* CSD Version 2.0 */
|
|
erase_unit_sz = 1;
|
|
}
|
|
else {
|
|
/* CSD Version 1.0 */
|
|
if (mmc_csd->erase_blk_len)
|
|
erase_unit_sz = 1;
|
|
else
|
|
erase_unit_sz = mmc_csd->sector_size;
|
|
}
|
|
|
|
}
|
|
|
|
uint32_t num_blocks = len / MMC_BLK_SZ;
|
|
|
|
if (num_blocks < erase_unit_sz) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Requested length is less than min erase group size\r\n");
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
/* Calculate erase groups based on the length in blocks */
|
|
num_erase_grps = num_blocks / erase_unit_sz;
|
|
|
|
/* Start address of the erase range */
|
|
erase_start = blk_addr;
|
|
|
|
if (MMC_CARD_MMC(card))
|
|
/* Last address of the erase range */
|
|
erase_end = blk_addr + ((num_erase_grps - 1) * erase_unit_sz);
|
|
else
|
|
erase_end = blk_addr + num_blocks - 1;
|
|
|
|
/* Boundary check for overlap */
|
|
blk_end = blk_addr + num_blocks;
|
|
|
|
if (erase_end > blk_end) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"The erase group overlaps the max requested for erase\r\n");
|
|
erase_end -= erase_unit_sz;
|
|
}
|
|
|
|
/* Send CMD35 for erase group start */
|
|
mmc_ret = sdrv_mmc_send_erase_grp_start(dev, erase_start);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to send erase grp start address\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
/* Send CMD36 for erase group end */
|
|
mmc_ret = sdrv_mmc_send_erase_grp_end(dev, erase_end);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to send erase grp end address\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
if (MMC_CARD_MMC(card)) {
|
|
/*
|
|
* As per emmc 4.5 spec section 7.4.27, calculate the erase timeout
|
|
* erase_timeout = 300ms * ERASE_TIMEOUT_MULT * num_erase_grps
|
|
*/
|
|
erase_timeout = (300 * 1000 * card->ext_csd[MMC_ERASE_TIMEOUT_MULT] *
|
|
num_erase_grps);
|
|
|
|
}
|
|
else {
|
|
/*
|
|
* For sd card, calculate the erase timeout
|
|
* erase_timeout = (earse_timeout * num_erase_grps + erase_offset) * 1s
|
|
*/
|
|
erase_timeout =
|
|
card->ssr.erase_timeout * num_erase_grps + card->ssr.erase_offset;
|
|
erase_timeout = MAX(erase_timeout, 1);
|
|
erase_timeout *= 1000 * 1000;
|
|
DETAIL_PRINTF(SSDK_DEBUG,
|
|
"\r\n ssr erase_timeout %d, num_erase_grps %d, ssr erase_offset %d\r\n",
|
|
card->ssr.erase_timeout, num_erase_grps, card->ssr.erase_offset);
|
|
}
|
|
|
|
/* Send CMD38 to perform erase */
|
|
mmc_ret = sdrv_mmc_send_erase(dev, erase_timeout);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to erase the specified partition\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc sdhci cancel
|
|
* Arg : mmc device structure
|
|
* Return : 0 on Success, non zero on failure
|
|
* Flow : call sdrv_mmc_stop_command
|
|
*/
|
|
status_t sdrv_mmc_sdhci_cancel(struct mmc_device *dev)
|
|
{
|
|
return sdrv_mmc_stop_command(dev);
|
|
}
|
|
|
|
/*
|
|
* Function: mmc get wp status
|
|
* Arg : mmc device structure, block address and buffer for getting wp
|
|
* status Return : 0 on Success, 1 on Failure Flow : Get the WP group status
|
|
* by sending CMD31
|
|
*/
|
|
status_t sdrv_mmc_get_wp_status(struct mmc_device *dev, uint32_t addr,
|
|
uint8_t *wp_status)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
cmd.cmd_index = CMD31_SEND_WRITE_PROT_TYPE;
|
|
cmd.argument = addr;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.data_present = 0x1;
|
|
cmd.data.data_ptr = wp_status;
|
|
cmd.data.num_blocks = 0x1;
|
|
cmd.data.blk_sz = 0x8;
|
|
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to get status of write protect bits\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
/*
|
|
* Function: mmc set/clear WP on user area
|
|
* Arg : mmc device structure, block address,len, & flag to set or clear
|
|
* Return : 0 on success, 1 on failure
|
|
* Flow : Function to set/clear power on write protect on user area
|
|
*/
|
|
|
|
status_t sdrv_mmc_set_clr_power_on_wp_user(struct mmc_device *dev,
|
|
uint32_t addr,
|
|
uint64_t len, uint8_t set_clr)
|
|
{
|
|
struct mmc_command cmd;
|
|
struct mmc_card *card = &dev->card;
|
|
uint32_t wp_grp_size;
|
|
uint32_t status;
|
|
uint32_t num_wp_grps;
|
|
status_t ret = SDRV_STATUS_OK;
|
|
uint32_t retry = 0;
|
|
uint32_t i;
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
|
|
/* Convert len into blocks */
|
|
len = len / MMC_BLK_SZ;
|
|
wp_grp_size = dev->card.wp_grp_size;
|
|
|
|
/* Disable PERM WP */
|
|
ret = sdrv_mmc_switch_cmd(&dev->host, &dev->card, MMC_SET_BIT, MMC_USR_WP,
|
|
MMC_US_PERM_WP_DIS);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to Disable PERM WP\r\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Read the default values for user WP */
|
|
ret = sdrv_mmc_get_ext_csd(&dev->host, &dev->card);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to read ext csd for the card\r\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Check if user power on WP is disabled or perm WP is enabled */
|
|
if ((dev->card.ext_csd[MMC_USR_WP] & MMC_US_PWR_WP_DIS) ||
|
|
(dev->card.ext_csd[MMC_USR_WP] & MMC_US_PERM_WP_EN)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Power on protection is disabled, cannot be set\r\n");
|
|
return SDRV_STATUS_FAIL;
|
|
}
|
|
|
|
if (len < wp_grp_size) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Length is less than min WP size, WP was not set\r\n");
|
|
return SDRV_STATUS_FAIL;
|
|
}
|
|
|
|
/* Set power on USER WP */
|
|
ret = sdrv_mmc_switch_cmd(&dev->host, &dev->card, MMC_SET_BIT, MMC_USR_WP,
|
|
MMC_US_PWR_WP_EN);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to set power on WP for user\r\n");
|
|
return ret;
|
|
}
|
|
|
|
num_wp_grps = ROUNDUP(len, wp_grp_size) / wp_grp_size;
|
|
|
|
if (set_clr)
|
|
cmd.cmd_index = CMD28_SET_WRITE_PROTECT;
|
|
else
|
|
cmd.cmd_index = CMD29_CLEAR_WRITE_PROTECT;
|
|
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1B;
|
|
|
|
for (i = 0; i < num_wp_grps; i++) {
|
|
/*
|
|
* Standard emmc cards use byte mode addressing
|
|
* convert the block address to byte address before
|
|
* sending the command
|
|
*/
|
|
if (card->type == MMC_TYPE_STD_MMC)
|
|
cmd.argument = (addr + (i * wp_grp_size)) * card->block_size;
|
|
else
|
|
cmd.argument = addr + (i * wp_grp_size);
|
|
|
|
ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* CMD28/CMD29 On failure returns address out of range error */
|
|
if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0])) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Address for CMD28/29 is out of range\r\n");
|
|
return SDRV_STATUS_MMC_CMD_RSP_ERR;
|
|
}
|
|
|
|
/* Check the card status */
|
|
do {
|
|
ret = sdrv_mmc_get_card_status(&dev->host, &dev->card, &status);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Failed to get card status afterapplying write protect\r\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Time out for WP command */
|
|
retry++;
|
|
udelay(1000);
|
|
|
|
if (retry == MMC_MAX_CARD_STAT_RETRY) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Card status timed out after sending write protect command\r\n");
|
|
return SDRV_STATUS_TIMEOUT;
|
|
}
|
|
}
|
|
while (!(status & MMC_READY_FOR_DATA) ||
|
|
(MMC_CARD_STATUS(status) == MMC_PROG_STATE));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Function to put the mmc card to sleep */
|
|
void sdrv_mmc_put_card_to_sleep(struct mmc_device *dev)
|
|
{
|
|
struct mmc_command cmd = {0};
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
cmd.cmd_index = CMD7_SELECT_DESELECT_CARD;
|
|
cmd.argument = 0x00000000;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_NONE;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
/* send command */
|
|
if (sdrv_sdhci_send_command(&dev->host, &cmd)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "card deselect error: %s\r\n", __func__);
|
|
return;
|
|
}
|
|
|
|
cmd.cmd_index = CMD5_SLEEP_AWAKE;
|
|
cmd.argument = (card->rca << MMC_CARD_RCA_BIT) | MMC_CARD_SLEEP;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1B;
|
|
|
|
/* send command */
|
|
if (sdrv_sdhci_send_command(&dev->host, &cmd))
|
|
DETAIL_PRINTF(SSDK_CRIT, "card sleep error: %s\r\n", __func__);
|
|
}
|
|
|
|
static status_t sdrv_mmc_can_poweroff_notify(struct mmc_device *dev)
|
|
{
|
|
struct mmc_card *card;
|
|
|
|
card = &dev->card;
|
|
return MMC_CARD_MMC(card)
|
|
&& card->ext_csd[MMC_POWER_OFF_NOTIFICATION] == MMC_POWER_ON;
|
|
}
|
|
|
|
/* Function to power off Notification mmc card */
|
|
status_t sdrv_mmc_power_off_notification(struct mmc_device *dev,
|
|
uint32_t pon_type)
|
|
{
|
|
status_t ret = SDRV_STATUS_OK;
|
|
|
|
ret = sdrv_mmc_can_poweroff_notify(dev);
|
|
|
|
if (ret == false) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "emmc not support power off notification\r\n");
|
|
return SDRV_STATUS_FAIL;
|
|
}
|
|
|
|
ret = sdrv_mmc_switch_cmd(&dev->host, &dev->card, MMC_ACCESS_WRITE,
|
|
MMC_POWER_OFF_NOTIFICATION, pon_type);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to power off notification: %u\r\n", pon_type);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Switch the partition access type to rpmb or default
|
|
*/
|
|
status_t sdrv_mmc_sdhci_switch_part(struct mmc_device *dev, uint32_t type)
|
|
{
|
|
uint32_t part_access;
|
|
status_t ret = SDRV_STATUS_OK;
|
|
struct mmc_card *card;
|
|
|
|
card = &dev->card;
|
|
|
|
if (!MMC_CARD_MMC(card))
|
|
return SDRV_STATUS_MMC_DEV_TYPE_ERR;
|
|
|
|
/* Clear the partition access */
|
|
part_access =
|
|
dev->card.ext_csd[MMC_PARTITION_CONFIG] & ~PARTITION_ACCESS_MASK;
|
|
part_access |= type;
|
|
|
|
ret = sdrv_mmc_switch_cmd(&dev->host, &dev->card, MMC_ACCESS_WRITE,
|
|
MMC_PARTITION_CONFIG, part_access);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to switch partition to type: %u\r\n", type);
|
|
return ret;
|
|
}
|
|
|
|
dev->card.ext_csd[MMC_PARTITION_CONFIG] = part_access;
|
|
return ret;
|
|
}
|
|
|
|
static status_t sdrv_mmc_sdhci_set_blk_cnt(struct mmc_device *dev,
|
|
uint32_t blk_cnt,
|
|
uint32_t rel_write)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
struct mmc_command cmd = {0};
|
|
|
|
cmd.cmd_index = CMD23_SET_BLOCK_COUNT;
|
|
cmd.argument = blk_cnt & MMC_BLOCK_COUNT_MASK;
|
|
cmd.argument |= rel_write;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.retries = MMC_CMD_RETRIES;
|
|
|
|
mmc_ret = sdrv_sdhci_send_command(&dev->host, &cmd);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Set block count failed: %s\r\n", __func__);
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|
|
|
|
status_t sdrv_mmc_sdhci_rpmb_send(struct mmc_device *dev,
|
|
struct mmc_command *cmd)
|
|
{
|
|
int i;
|
|
uint32_t retry = 5;
|
|
uint32_t status;
|
|
uint32_t rel_write = 0;
|
|
status_t ret = SDRV_STATUS_OK;
|
|
|
|
ASSERT(cmd);
|
|
|
|
/* 1. Set the partition type to rpmb */
|
|
ret = sdrv_mmc_sdhci_switch_part(dev, PART_ACCESS_RPMB);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < MAX_RPMB_CMDS; i++) {
|
|
if (!cmd[i].cmd_index)
|
|
break;
|
|
|
|
if (cmd[i].write_flag == true)
|
|
rel_write = BIT(31);
|
|
else
|
|
rel_write = 0;
|
|
|
|
/* 2. Set the block count using cmd23 */
|
|
ret = sdrv_mmc_sdhci_set_blk_cnt(dev, cmd[i].data.num_blocks, rel_write);
|
|
|
|
if (ret)
|
|
goto err;
|
|
|
|
/* 3. Send the command */
|
|
ret = sdrv_sdhci_send_command(&dev->host, &cmd[i]);
|
|
|
|
if (ret)
|
|
goto err;
|
|
|
|
do {
|
|
/* 4. Poll for card status to ensure rpmb operation completeness */
|
|
ret = sdrv_mmc_get_card_status(&dev->host, &dev->card, &status);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Failed to get card status after rpmb operations\r\n");
|
|
goto err;
|
|
}
|
|
|
|
retry--;
|
|
udelay(500);
|
|
|
|
if (!retry) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"Card status check timed out after rpmb operations\r\n");
|
|
ret = SDRV_STATUS_TIMEOUT;
|
|
goto err;
|
|
}
|
|
}
|
|
while (!(status & MMC_READY_FOR_DATA) ||
|
|
(MMC_CARD_STATUS(status) == MMC_PROG_STATE));
|
|
}
|
|
|
|
/* If we reach here, that means success */
|
|
ret = SDRV_STATUS_OK;
|
|
|
|
err:
|
|
|
|
/* 5. Switch the partition back to default type */
|
|
if (sdrv_mmc_sdhci_switch_part(dev, PART_ACCESS_DEFAULT))
|
|
return ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
status_t sdrv_mmc_sdhci_read_part(struct mmc_device *dev, void *dest,
|
|
uint32_t blk_addr,
|
|
uint32_t num_blocks, uint32_t type)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
if ((NULL == dev) || (NULL == dest)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "The passed pointer is null\n");
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
uint64_t mmc_size = sdrv_mmc_get_size(dev, type);
|
|
|
|
if (mmc_size == 0) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "mmc part %d not support\n", type);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (!IS_ALIGNED(dest, CONFIG_ARCH_CACHE_LINE)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "dest address %p not aligned to cache line %d\n", dest,
|
|
CONFIG_ARCH_CACHE_LINE);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (num_blocks == 0
|
|
|| ((blk_addr + num_blocks) > (mmc_size / card->block_size))) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"num_blocks == 0 or blk_addr + num_blocks out of the mmc size range\n");
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"blk_addr:%x num_blocks:%x mmc block size:%llx\n", blk_addr, num_blocks,
|
|
(mmc_size / card->block_size));
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (MMC_CARD_SD(card) && dev->part_curr != PART_ACCESS_DEFAULT) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"sd card only support access default part, part type %d\r\n", type);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
else if (MMC_CARD_MMC(card) && type != dev->part_curr) {
|
|
mmc_ret = sdrv_mmc_sdhci_switch_part(dev, type);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "mmc switch %d part error\r\n", type);
|
|
return mmc_ret;
|
|
}
|
|
|
|
dev->part_curr = type;
|
|
}
|
|
|
|
mmc_ret = sdrv_mmc_sdhci_read(dev, dest, blk_addr, num_blocks);
|
|
return mmc_ret;
|
|
}
|
|
|
|
status_t sdrv_mmc_sdhci_write_part(struct mmc_device *dev, void *src,
|
|
uint32_t blk_addr,
|
|
uint32_t num_blocks, uint32_t type)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
if ((NULL == dev) || (NULL == src)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "The passed pointer is null\n");
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
uint64_t mmc_size = sdrv_mmc_get_size(dev, type);
|
|
|
|
if (mmc_size == 0) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "mmc part %d not support\n", type);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
|
|
if (!IS_ALIGNED(src, CONFIG_ARCH_CACHE_LINE)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "src address %p not aligned to cache line %d\n", src,
|
|
CONFIG_ARCH_CACHE_LINE);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (num_blocks == 0
|
|
|| ((blk_addr + num_blocks) > (mmc_size / card->block_size))) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"num_blocks == 0 or blk_addr + num_blocks out of the mmc size range\n");
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"blk_addr:%x num_blocks:%x mmc block size:%llx\n", blk_addr, num_blocks,
|
|
(mmc_size / card->block_size));
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (MMC_CARD_SD(card) && dev->part_curr != PART_ACCESS_DEFAULT) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"sd card only support access default part, part type %d\r\n", type);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
else if (MMC_CARD_MMC(card) && type != dev->part_curr) {
|
|
mmc_ret = sdrv_mmc_sdhci_switch_part(dev, type);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "mmc switch %d part error\r\n", type);
|
|
return mmc_ret;
|
|
}
|
|
|
|
dev->part_curr = type;
|
|
}
|
|
|
|
mmc_ret = sdrv_mmc_sdhci_write(dev, src, blk_addr, num_blocks);
|
|
return mmc_ret;
|
|
}
|
|
|
|
status_t sdrv_mmc_sdhci_erase_part(struct mmc_device *dev,
|
|
uint32_t blk_addr,
|
|
uint32_t num_blocks, uint32_t type)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
if (NULL == dev) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "The passed pointer is null\n");
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
struct mmc_card *card = &dev->card;
|
|
|
|
uint64_t mmc_size = sdrv_mmc_get_size(dev, type);
|
|
|
|
uint32_t erase_group_size = sdrv_mmc_get_erase_group_size(dev);
|
|
|
|
if (mmc_size == 0) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "mmc part %d not support\n", type);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (num_blocks == 0
|
|
|| ((blk_addr + num_blocks) > (mmc_size / card->block_size))) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"num_blocks == 0 or blk_addr + num_blocks out of the mmc size range\n");
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"blk_addr:%x num_blocks:%x mmc block size:%llx\n", blk_addr, num_blocks,
|
|
(mmc_size / card->block_size));
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (!IS_ALIGNED(blk_addr, (erase_group_size / card->block_size))
|
|
|| !IS_ALIGNED(num_blocks, (erase_group_size / card->block_size))) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"blk_addr %x or num_blocks %x not aligned to erase group size %x\n",
|
|
blk_addr, num_blocks, erase_group_size / card->block_size);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
|
|
if (MMC_CARD_SD(card) && dev->part_curr != PART_ACCESS_DEFAULT) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"sd card only support access default part, part type %d\r\n", type);
|
|
return SDRV_STATUS_INVALID_PARAM;
|
|
}
|
|
else if (MMC_CARD_MMC(card) && type != dev->part_curr) {
|
|
mmc_ret = sdrv_mmc_sdhci_switch_part(dev, type);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "mmc switch %d part error\r\n", type);
|
|
return mmc_ret;
|
|
}
|
|
|
|
dev->part_curr = type;
|
|
}
|
|
|
|
mmc_ret = sdrv_mmc_sdhci_erase(dev, blk_addr,
|
|
(uint64_t)num_blocks * MMC_BLK_SZ);
|
|
return mmc_ret;
|
|
}
|
|
|
|
|
|
static status_t sdrv_sehc_set_clk(struct sdhci_host *host, unsigned int clock)
|
|
{
|
|
status_t ret = SDRV_STATUS_OK;
|
|
uint16_t clk_ctrl = 0;
|
|
struct mmc_device *mmc = host->parent;
|
|
struct mmc_config_data *mmc_cfg = &mmc->config;
|
|
sdrv_ckgen_node_t *clk = mmc_cfg->clk;
|
|
|
|
/* Disable the clock */
|
|
REG_WRITE16(host, clk_ctrl, SDHCI_CLK_CTRL_REG);
|
|
|
|
if (clock == 0u)
|
|
return SDRV_STATUS_OK;
|
|
|
|
/*
|
|
* DDR TIMING need twice base frequency.
|
|
*/
|
|
if (clk != NULL) {
|
|
if ((host->timing == SDHCI_EMMC_HS400_MODE)
|
|
|| (host->timing == SDHCI_EMMC_DDR52_MODE)
|
|
|| (host->timing == SDHCI_UHS_DDR50_MODE)) {
|
|
if (sdrv_ckgen_set_rate(clk, MIN(clock, host->max_clk_rate) * 2))
|
|
return SDRV_STATUS_FAIL;
|
|
}
|
|
else {
|
|
if (sdrv_ckgen_set_rate(clk, MIN(clock, host->max_clk_rate)))
|
|
return SDRV_STATUS_FAIL;
|
|
}
|
|
}
|
|
|
|
DETAIL_PRINTF(SSDK_INFO, "switch card clock to %d\r\n", MIN(clock,
|
|
host->max_clk_rate));
|
|
|
|
clk_ctrl |= SDHCI_INT_CLK_EN;
|
|
REG_WRITE16(host, clk_ctrl, SDHCI_CLK_CTRL_REG);
|
|
|
|
/* Check for clock stable, timeout 150ms */
|
|
ret =
|
|
sdrv_sdhci_wait_for_bit(host, SDHCI_CLK_CTRL_REG, SDHCI_CLK_STABLE, 0, 150);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error: sdhci clock wait stable timeout!");
|
|
return ret;
|
|
}
|
|
|
|
clk_ctrl |= SDHCI_CLK_PLL_EN;
|
|
REG_WRITE16(host, clk_ctrl, SDHCI_CLK_CTRL_REG);
|
|
|
|
/* Check for clock stable, timeout 150ms */
|
|
ret =
|
|
sdrv_sdhci_wait_for_bit(host, SDHCI_CLK_CTRL_REG, SDHCI_CLK_STABLE, 0, 150);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "Error: sdhci clock wait stable timeout!");
|
|
return ret;
|
|
}
|
|
|
|
/* Now clock is stable, enable it */
|
|
clk_ctrl = REG_READ16(host, SDHCI_CLK_CTRL_REG);
|
|
clk_ctrl |= SDHCI_CLK_EN;
|
|
REG_WRITE16(host, clk_ctrl, SDHCI_CLK_CTRL_REG);
|
|
host->cur_clk_rate = clock;
|
|
return ret;
|
|
}
|
|
|
|
static inline status_t sdrv_sehc_dll_config(struct sdhci_host *host)
|
|
{
|
|
status_t ret = SDRV_STATUS_OK;
|
|
uint16_t clk_ctrl = 0;
|
|
uint32_t dll_ctrl = 0;
|
|
|
|
DETAIL_PRINTF(SSDK_INFO, "sehc dll config!\r\n");
|
|
|
|
/* Disable the clock */
|
|
clk_ctrl = REG_READ16(host, SDHCI_CLK_CTRL_REG);
|
|
clk_ctrl &= ~SDHCI_CLK_EN;
|
|
REG_WRITE16(host, clk_ctrl, SDHCI_CLK_CTRL_REG);
|
|
|
|
/* config dll_ctrl */
|
|
dll_ctrl = REG_READ32(host, SDHCI_DLL_CTRL);
|
|
dll_ctrl |= SDHCI_DLL_EN;
|
|
dll_ctrl &= ~(SDHCI_DLL_SLV_TARGET_MASK << SDHCI_DLL_SLV_TARGET_OFFSET);
|
|
dll_ctrl |= (0x1f << SDHCI_DLL_SLV_TARGET_OFFSET);
|
|
REG_WRITE32(host, dll_ctrl, SDHCI_DLL_CTRL);
|
|
|
|
/* Enable the clock */
|
|
clk_ctrl |= SDHCI_CLK_EN;
|
|
REG_WRITE16(host, clk_ctrl, SDHCI_CLK_CTRL_REG);
|
|
|
|
ret = sdrv_sdhci_wait_for_bit(host, SDHCI_DLL_STATUS, SDHCI_DLL_REF_LOCK, 0,
|
|
200);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "sehc dll timeout!\r\n");
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void sdrv_sehc_set_uhs_mode(struct sdhci_host *host, uint32_t mode)
|
|
{
|
|
|
|
sdrv_sdhci_set_uhs_mode(host, mode);
|
|
|
|
#if CONFIG_E3L
|
|
|
|
/* taishanL DDR50 mode need config DAT_OE_MASK */
|
|
if (mode == SDHCI_UHS_DDR50_MODE) {
|
|
uint32_t oe_mask;
|
|
oe_mask = REG_READ32(host, SDHCI_OE_MASK_SEL_REG);
|
|
oe_mask &= ~SDHCI_DAT_OE_MASK_SEL;
|
|
oe_mask |= 0x1 << SDHCI_DAT_OE_OFFSET;
|
|
REG_WRITE32(host, oe_mask, SDHCI_OE_MASK_SEL_REG);
|
|
}
|
|
|
|
#endif
|
|
|
|
/* HS400 mode need config dll frequency */
|
|
if (mode == SDHCI_EMMC_HS400_MODE) {
|
|
if (sdrv_sehc_dll_config(host))
|
|
DETAIL_PRINTF(SSDK_CRIT, "sehc dll config failed!\r\n");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static bool sdrv_sehc_mmc_ending_trans(struct sdhci_host *host)
|
|
{
|
|
struct mmc_device *dev = host->parent;
|
|
struct mmc_config_data *mmc_cfg = &dev->config;
|
|
sdrv_scr_t *scr_ctr = mmc_cfg->scr_ctrl;
|
|
scr_signal_t *scr_signal = mmc_cfg->scr_signal;
|
|
|
|
if ((NULL == scr_ctr) || (NULL == scr_signal)) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"sehc ending trans check, not config scr_ctr || scr_signal!\r\n");
|
|
return true;
|
|
}
|
|
|
|
return (0x1 == scr_get(scr_ctr, scr_signal));
|
|
}
|
|
|
|
static void sdrv_sehc_hw_reset(struct sdhci_host *host)
|
|
{
|
|
uint32_t emmc_ctrl;
|
|
emmc_ctrl = REG_READ32(host, SDHCI_EMMC_CTRL_REG);
|
|
emmc_ctrl &= ~SDHCI_RST_N;
|
|
REG_WRITE32(host, emmc_ctrl, SDHCI_EMMC_CTRL_REG);
|
|
|
|
udelay(5);
|
|
emmc_ctrl |= SDHCI_RST_N;
|
|
REG_WRITE32(host, emmc_ctrl, SDHCI_EMMC_CTRL_REG);
|
|
}
|
|
|
|
static status_t sdrv_sehc_tuning_sequence(struct sdhci_host *host,
|
|
uint32_t opcode,
|
|
uint32_t bus_width)
|
|
{
|
|
status_t ret = SDRV_STATUS_OK;
|
|
uint32_t reg;
|
|
struct mmc_command cmd;
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
cmd.cmd_index = opcode;
|
|
cmd.argument = 0x0;
|
|
cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
|
|
cmd.resp_type = SDHCI_CMD_RESP_R1;
|
|
cmd.trans_mode = SDHCI_MMC_READ;
|
|
cmd.data_present = 0x0;
|
|
cmd.data.data_ptr = NULL;
|
|
cmd.data.num_blocks = 0x1;
|
|
|
|
if (cmd.cmd_index == CMD21_SEND_TUNING_BLOCK &&
|
|
bus_width == MMC_DATA_BUS_WIDTH_8BIT)
|
|
cmd.data.blk_sz = 128;
|
|
else
|
|
cmd.data.blk_sz = 64;
|
|
|
|
REG_WRITE16(host, cmd.data.blk_sz, SDHCI_BLKSZ_REG);
|
|
|
|
if (host->caps.spec_version >= SDHCI_SPEC_VER4_NUM)
|
|
REG_WRITE32(host, cmd.data.num_blocks, SDHCI_SDMASA_BLKCNT_REG);
|
|
else
|
|
REG_WRITE16(host, cmd.data.num_blocks, SDHCI_BLK_CNT_REG);
|
|
|
|
/* Write to transfer mode register */
|
|
REG_WRITE16(host, SDHCI_READ_MODE, SDHCI_TRANS_MODE_REG);
|
|
|
|
for (int i = 0; i < SEHC_MAX_TUNING_LOOP; i++) {
|
|
ret = sdrv_sdhci_send_command(host, &cmd);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "sdhci send hw tuning cmd error!\r\n");
|
|
return ret;
|
|
}
|
|
|
|
/* wait for read buf ready, timeout 50ms */
|
|
ret = sdrv_sdhci_wait_for_bit(host, SDHCI_NRML_INT_STS_REG,
|
|
SDHCI_INT_DATA_AVAIL, 0, 50);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "sdhci tuning timeout!\r\n");
|
|
return ret;
|
|
}
|
|
|
|
reg = REG_READ16(host, SDHCI_HOST_CTRL2_REG);
|
|
|
|
if (!(reg & SDHCI_EXEC_TUNING)) {
|
|
/* check SDHCI_SAMPLE_CLK_SEL set,tuning is successful */
|
|
if (reg & SDHCI_SAMPLE_CLK_SEL) {
|
|
DETAIL_PRINTF(SSDK_INFO, "sdhci: tuning cycle count = %d\r\n", i);
|
|
|
|
if (i < SDHCI_HW_TUNING_MIN_COUNT)
|
|
DETAIL_PRINTF(SSDK_CRIT, "SDHC: tuning cycle abnormal, num is %d!\r\n", i);
|
|
|
|
return SDRV_STATUS_OK;
|
|
}
|
|
|
|
/* check FAIL_NUM_ > 0,also tuning is successful */
|
|
reg = REG_READ32(host, SDHCI_TUNING_STATUS_REG);
|
|
|
|
if ((reg >> SDHCI_FAIL_NUM_OFFSET) & SDHCI_FAIL_NUM_MASK) {
|
|
DETAIL_PRINTF(SSDK_INFO, "sdhci: tuning cycle count = %d\r\n", i);
|
|
|
|
if (i < 40)
|
|
DETAIL_PRINTF(SSDK_CRIT, "SDHC: tuning cycle abnormal, num is %d!\r\n", i);
|
|
|
|
DETAIL_PRINTF(SSDK_INFO, "sdhci: tuning sw config override\r\n");
|
|
|
|
uint32_t fail_num = 0;
|
|
uint32_t pass_num = 0;
|
|
uint32_t override_val = 0;
|
|
|
|
fail_num = (reg >> SDHCI_FAIL_NUM_OFFSET) & SDHCI_FAIL_NUM_MASK;
|
|
pass_num = (reg >> SDHCI_PASS_NUM_OFFSET) & SDHCI_PASS_NUM_MASK;
|
|
override_val = (fail_num + pass_num) / 2;
|
|
|
|
/* set override */
|
|
reg = REG_READ32(host, SDHCI_TUNING_CTRL_REG);
|
|
reg |= SDHCI_OVERRIDE_EN;
|
|
reg &= ~(SDHCI_OVERRIDE_VAL_MASK << SDHCI_OVERRIDE_VAL_OFFSET);
|
|
reg |= override_val << SDHCI_OVERRIDE_VAL_OFFSET;
|
|
REG_WRITE32(host, reg, SDHCI_TUNING_CTRL_REG);
|
|
/* set SAMPLE_CLK_SEL */
|
|
reg = REG_READ16(host, SDHCI_HOST_CTRL2_REG);
|
|
reg |= SDHCI_SAMPLE_CLK_SEL;
|
|
REG_WRITE16(host, reg, SDHCI_HOST_CTRL2_REG);
|
|
|
|
return SDRV_STATUS_OK;
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return SDRV_STATUS_MMC_TUNING_FAIL;
|
|
}
|
|
|
|
status_t sdrv_sehc_execute_tuning(struct sdhci_host *host, uint32_t opcode,
|
|
uint32_t bus_width)
|
|
{
|
|
status_t ret = SDRV_STATUS_OK;
|
|
uint32_t reg;
|
|
|
|
/* reset the tuning circuit */
|
|
sdrv_sdhci_reset_tuning(host);
|
|
|
|
/* set tuning_clk_stop_en */
|
|
reg = REG_READ32(host, SDHCI_TUNING_CTRL_REG);
|
|
reg |= SDHCI_TUNING_CLK_STOP_EN;
|
|
REG_WRITE32(host, reg, SDHCI_TUNING_CTRL_REG);
|
|
|
|
/* start the tuning circuit */
|
|
sdrv_sdhci_start_tuning(host);
|
|
host->tuning_in_progress = 1;
|
|
udelay(1);
|
|
|
|
/* disable SDHCI_INT_DATA_AVAIL interrupt */
|
|
reg = REG_READ16(host, SDHCI_NRML_INT_SIG_EN_REG);
|
|
reg &= ~SDHCI_INT_DATA_AVAIL;
|
|
REG_WRITE16(host, reg, SDHCI_NRML_INT_SIG_EN_REG);
|
|
|
|
ret = sdrv_sehc_tuning_sequence(host, opcode, bus_width);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "sdhci tuning failed, abort tuning state!\r\n");
|
|
sdrv_sdhci_abort_tuning(host);
|
|
}
|
|
|
|
/* clear SDHCI_INT_DATA_AVAIL */
|
|
REG_WRITE16(host, SDHCI_INT_DATA_AVAIL, SDHCI_NRML_INT_STS_REG);
|
|
/* enable SDHCI_INT_DATA_AVAIL interrupt */
|
|
reg = REG_READ16(host, SDHCI_NRML_INT_SIG_EN_REG);
|
|
reg |= SDHCI_INT_DATA_AVAIL;
|
|
REG_WRITE16(host, reg, SDHCI_NRML_INT_SIG_EN_REG);
|
|
|
|
/* clear tuning_clk_stop_en */
|
|
reg = REG_READ32(host, SDHCI_TUNING_CTRL_REG);
|
|
reg &= ~SDHCI_TUNING_CLK_STOP_EN;
|
|
REG_WRITE32(host, reg, SDHCI_TUNING_CTRL_REG);
|
|
|
|
host->tuning_in_progress = 0;
|
|
return ret;
|
|
}
|
|
|
|
static void sdrv_sehc_prepare_tuning(struct sdhci_host *host, uint32_t val)
|
|
{
|
|
uint32_t reg;
|
|
uint16_t clk = 0;
|
|
|
|
clk = REG_READ16(host, SDHCI_CLK_CTRL_REG);
|
|
clk &= ~SDHCI_CLK_EN;
|
|
REG_WRITE16(host, clk, SDHCI_CLK_CTRL_REG);
|
|
|
|
reg = REG_READ32(host, SDHCI_TUNING_CTRL_REG);
|
|
reg |= SDHCI_OVERRIDE_EN;
|
|
reg &= ~(SDHCI_OVERRIDE_VAL_MASK << SDHCI_OVERRIDE_VAL_OFFSET);
|
|
reg |= val << SDHCI_OVERRIDE_VAL_OFFSET;
|
|
REG_WRITE32(host, reg, SDHCI_TUNING_CTRL_REG);
|
|
|
|
clk = REG_READ16(host, SDHCI_CLK_CTRL_REG);
|
|
clk |= SDHCI_CLK_EN;
|
|
REG_WRITE16(host, clk, SDHCI_CLK_CTRL_REG);
|
|
}
|
|
|
|
__UNUSED static void sdrv_sehc_post_tuning(struct sdhci_host *host)
|
|
{
|
|
sdrv_sdhci_reset_tuning(host);
|
|
}
|
|
|
|
status_t sdrv_sehc_sw_execute_tuning(struct sdhci_host *host, uint32_t opcode,
|
|
uint32_t bus_width)
|
|
{
|
|
status_t ret = SDRV_STATUS_OK;
|
|
uint32_t min = 0;
|
|
uint32_t max = 0;
|
|
uint32_t avg = 0;
|
|
uint32_t reg = 0;
|
|
|
|
/* reset the tuning circuit */
|
|
sdrv_sdhci_reset_tuning(host);
|
|
|
|
/* set tuning_clk_stop_en */
|
|
reg = REG_READ32(host, SDHCI_TUNING_CTRL_REG);
|
|
reg |= SDHCI_TUNING_CLK_STOP_EN;
|
|
REG_WRITE32(host, reg, SDHCI_TUNING_CTRL_REG);
|
|
|
|
/* config the sample tuning circuit */
|
|
uint16_t ctrl2;
|
|
ctrl2 = REG_READ16(host, SDHCI_HOST_CTRL2_REG);
|
|
ctrl2 |= SDHCI_SAMPLE_CLK_SEL;
|
|
REG_WRITE16(host, ctrl2, SDHCI_HOST_CTRL2_REG);
|
|
|
|
host->sw_tuning_in_progress = 1;
|
|
udelay(1);
|
|
|
|
/* find the mininum delay first which can not pass tuning */
|
|
min = SEHC_TUNE_CTRL_MIN;
|
|
|
|
#if (CONFIG_E3 || CONFIG_D3)
|
|
|
|
if (IS_P0)
|
|
min = SEHC_1_0_TUNE_CTRL_MIN;
|
|
|
|
#endif
|
|
|
|
while (min < SEHC_TUNE_CTRL_MAX) {
|
|
sdrv_sehc_prepare_tuning(host, min);
|
|
|
|
if (sdrv_mmc_send_tuning((struct mmc_device *)host->parent, opcode, bus_width))
|
|
break;
|
|
|
|
min += SEHC_TUNE_CTRL_STEP;
|
|
}
|
|
|
|
DETAIL_PRINTF(SSDK_INFO, "sw tuning not pass tuning %d\r\n", min);
|
|
|
|
/* find the mininum delay first which can pass tuning */
|
|
min = min + SEHC_TUNE_CTRL_STEP;
|
|
SW_TUNING:
|
|
|
|
while (min < SEHC_TUNE_CTRL_MAX) {
|
|
sdrv_sehc_prepare_tuning(host, min);
|
|
|
|
if (!sdrv_mmc_send_tuning((struct mmc_device *)host->parent, opcode, bus_width))
|
|
break;
|
|
|
|
min += SEHC_TUNE_CTRL_STEP;
|
|
}
|
|
|
|
DETAIL_PRINTF(SSDK_INFO, "sw tuning pass tuning %d\r\n", min);
|
|
|
|
/* find the mininum delay first which can not pass tuning */
|
|
max = min + SEHC_TUNE_CTRL_STEP;
|
|
|
|
while (max < SEHC_TUNE_CTRL_MAX) {
|
|
sdrv_sehc_prepare_tuning(host, max);
|
|
|
|
if (sdrv_mmc_send_tuning((struct mmc_device *)host->parent, opcode, bus_width))
|
|
break;
|
|
|
|
max += SEHC_TUNE_CTRL_STEP;
|
|
}
|
|
|
|
DETAIL_PRINTF(SSDK_INFO, "sw tuning not pass tuning %d\r\n", max);
|
|
|
|
if (min >= SEHC_TUNE_CTRL_MAX) {
|
|
ret = SDRV_STATUS_MMC_TUNING_FAIL;
|
|
DETAIL_PRINTF(SSDK_CRIT, "sw tuning find pass window error\r\n");
|
|
goto out;
|
|
}
|
|
|
|
if ((max - min) > SEHC_TUNE_WINDOW_MIN) {
|
|
avg = (min + max) / 2;
|
|
DETAIL_PRINTF(SSDK_INFO, "sw tuning window %d ~ %d avg:%d\r\n", min, max, avg);
|
|
sdrv_sehc_prepare_tuning(host, avg);
|
|
|
|
ret = sdrv_mmc_send_tuning((struct mmc_device *)host->parent, opcode,
|
|
bus_width);
|
|
|
|
if (ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT,
|
|
"sw tuning window %d ~ %d avg:%d, tuning cmd error\r\n",
|
|
min, max, avg);
|
|
goto out;
|
|
}
|
|
|
|
}
|
|
else {
|
|
min = max + SEHC_TUNE_CTRL_STEP;
|
|
DETAIL_PRINTF(SSDK_INFO, "sw tuning window < 5,goto sw_tuning\r\n");
|
|
goto SW_TUNING;
|
|
}
|
|
|
|
out:
|
|
/* clear tuning_clk_stop_en */
|
|
reg = REG_READ32(host, SDHCI_TUNING_CTRL_REG);
|
|
reg &= ~SDHCI_TUNING_CLK_STOP_EN;
|
|
REG_WRITE32(host, reg, SDHCI_TUNING_CTRL_REG);
|
|
|
|
host->sw_tuning_in_progress = 0;
|
|
return ret;
|
|
|
|
}
|
|
|
|
struct sdhci_ops sdhci_sehc_ops = {
|
|
.hw_reset = sdrv_sehc_hw_reset,
|
|
.set_clock = sdrv_sehc_set_clk,
|
|
.set_uhs_mode = sdrv_sehc_set_uhs_mode,
|
|
.mmc_ending_trans = sdrv_sehc_mmc_ending_trans,
|
|
.platform_execute_tuning = sdrv_sehc_execute_tuning,
|
|
};
|
|
|
|
status_t sdrv_mmc_platform_init(struct mmc_device *dev, void *priv_data)
|
|
{
|
|
status_t mmc_ret = SDRV_STATUS_OK;
|
|
|
|
if (NULL == dev) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "\r\n %s: mmc_device parameter is null\r\n", __func__);
|
|
return SDRV_STATUS_INVALID_PARAM ;
|
|
}
|
|
|
|
struct sdhci_host *host;
|
|
|
|
struct mmc_config_data *mmc_cfg = &dev->config;
|
|
|
|
sdrv_ckgen_node_t *clk = mmc_cfg->clk;
|
|
|
|
struct mmc_sehc *sehc = priv_data;
|
|
|
|
host = &dev->host;
|
|
|
|
host->priv_data = sehc;
|
|
|
|
host->ops = &sdhci_sehc_ops;
|
|
|
|
host->parent = dev;
|
|
|
|
#if (CONFIG_E3 || CONFIG_D3)
|
|
if (IS_P0)
|
|
host->ops->platform_execute_tuning = sdrv_sehc_sw_execute_tuning;
|
|
|
|
#endif
|
|
|
|
if (clk != NULL) {
|
|
/* Init the ref clock: 400Khz */
|
|
if (sdrv_ckgen_set_rate(clk, 400000)) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "mmc set ref clock failed!\r\n");
|
|
return SDRV_STATUS_FAIL;
|
|
}
|
|
}
|
|
|
|
mmc_ret = sdrv_mmc_sdrv_sdhci_init(dev);
|
|
|
|
if (mmc_ret) {
|
|
DETAIL_PRINTF(SSDK_CRIT, "mmc init failed!\r\n");
|
|
return mmc_ret;
|
|
}
|
|
|
|
return mmc_ret;
|
|
}
|