mirror of
https://git.codelinaro.org/clo/qsdk/oss/boot/u-boot-2016.git
synced 2025-12-10 07:44:53 +01:00
The Device will accept the requested number of data blocks, terminate the transaction and return to transfer state. Stop command is not required at the end of this type of multiple blocks write unless terminated with an error. This change will send the STOP_TRANIMISSION command if the command failed. Change-Id: I9bd419ab8931d80a4a2eeba9f5bd42222257a824 Signed-off-by: Vinoth Gnanasekaran <vgnana@codeaurora.org>
265 lines
5.6 KiB
C
265 lines
5.6 KiB
C
/*
|
|
* Copyright 2008, Freescale Semiconductor, Inc
|
|
* Andy Fleming
|
|
*
|
|
* Based vaguely on the Linux code
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <common.h>
|
|
#include <part.h>
|
|
#include <div64.h>
|
|
#include <linux/math64.h>
|
|
#include <linux/log2.h>
|
|
#include "mmc_private.h"
|
|
|
|
static ulong mmc_erase_t(struct mmc *mmc, ulong start, lbaint_t blkcnt, int arg)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
ulong end;
|
|
int err, start_cmd, end_cmd;
|
|
|
|
if (mmc->high_capacity) {
|
|
end = start + blkcnt - 1;
|
|
} else {
|
|
end = (start + blkcnt - 1) * mmc->write_bl_len;
|
|
start *= mmc->write_bl_len;
|
|
}
|
|
|
|
if (IS_SD(mmc)) {
|
|
start_cmd = SD_CMD_ERASE_WR_BLK_START;
|
|
end_cmd = SD_CMD_ERASE_WR_BLK_END;
|
|
} else {
|
|
start_cmd = MMC_CMD_ERASE_GROUP_START;
|
|
end_cmd = MMC_CMD_ERASE_GROUP_END;
|
|
}
|
|
|
|
cmd.cmdidx = start_cmd;
|
|
cmd.cmdarg = start;
|
|
cmd.resp_type = MMC_RSP_R1;
|
|
|
|
err = mmc_send_cmd(mmc, &cmd, NULL);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
cmd.cmdidx = end_cmd;
|
|
cmd.cmdarg = end;
|
|
|
|
err = mmc_send_cmd(mmc, &cmd, NULL);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
cmd.cmdidx = MMC_CMD_ERASE;
|
|
cmd.cmdarg = arg;
|
|
cmd.resp_type = MMC_RSP_R1b;
|
|
|
|
err = mmc_send_cmd(mmc, &cmd, NULL);
|
|
if (err)
|
|
goto err_out;
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
puts("mmc erase failed\n");
|
|
return err;
|
|
}
|
|
|
|
unsigned long mmc_berase(int dev_num, lbaint_t start, lbaint_t blkcnt)
|
|
{
|
|
int err = 0;
|
|
u32 start_rem, blkcnt_rem;
|
|
struct mmc *mmc = find_mmc_device(dev_num);
|
|
lbaint_t blk = 0, blk_r = 0;
|
|
int timeout;
|
|
unsigned int qty = 0;
|
|
int arg = MMC_ERASE_ARG;
|
|
unsigned long erase_shift;
|
|
|
|
if (!mmc)
|
|
return -1;
|
|
|
|
timeout = mmc->trim_timeout;
|
|
if (!(mmc->sec_feature_support & EXT_CSD_SEC_ER_EN)) {
|
|
return -1;
|
|
}
|
|
|
|
if (is_power_of_2(mmc->erase_grp_size))
|
|
erase_shift = __ffs(mmc->erase_grp_size) - 1;
|
|
else
|
|
erase_shift = 0;
|
|
|
|
if (erase_shift)
|
|
qty += (((start+blkcnt) >> erase_shift) -
|
|
(start >> erase_shift)) + 1;
|
|
else if (IS_SD(mmc))
|
|
qty += blkcnt;
|
|
else
|
|
qty += (((start+blkcnt) / mmc->erase_grp_size) -
|
|
(start / mmc->erase_grp_size)) + 1;
|
|
|
|
timeout *= qty;
|
|
/*
|
|
* We want to see if the requested start or total block count are
|
|
* unaligned. We discard the whole numbers and only care about the
|
|
* remainder.
|
|
*/
|
|
err = div_u64_rem(start, mmc->erase_grp_size, &start_rem);
|
|
err = div_u64_rem(blkcnt, mmc->erase_grp_size, &blkcnt_rem);
|
|
if (start_rem || blkcnt_rem) {
|
|
if (mmc->sec_feature_support & EXT_CSD_SEC_GB_CL_EN) {
|
|
arg = MMC_SECURE_TRIM1_ARG;
|
|
} else {
|
|
printf("\n\nCaution! Your devices Erase group is 0x%x\n"
|
|
"The erase range would be change to "
|
|
"0x" LBAF "~0x" LBAF "\n\n",
|
|
mmc->erase_grp_size, start & ~(mmc->erase_grp_size - 1),
|
|
((start + blkcnt + mmc->erase_grp_size)
|
|
& ~(mmc->erase_grp_size - 1)) - 1);
|
|
}
|
|
}
|
|
|
|
if (arg != MMC_SECURE_TRIM1_ARG) {
|
|
while (blk < blkcnt) {
|
|
|
|
blk_r = ((blkcnt - blk) > mmc->erase_grp_size) ?
|
|
mmc->erase_grp_size : (blkcnt - blk);
|
|
|
|
err = mmc_erase_t(mmc, start + blk, blk_r, arg);
|
|
|
|
if (err)
|
|
break;
|
|
|
|
blk += blk_r;
|
|
|
|
/* Waiting for the ready status */
|
|
if (mmc_send_status(mmc, timeout))
|
|
return 0;
|
|
}
|
|
|
|
return blk;
|
|
} else {
|
|
if ( mmc->quirks & MMC_QUIRK_SECURE_TRIM ) {
|
|
arg = MMC_TRIM_ARG;
|
|
err = mmc_erase_t(mmc, start, blkcnt, arg);
|
|
|
|
if (err)
|
|
return -1;
|
|
|
|
/* Waiting for the ready status */
|
|
if (mmc_send_status(mmc, timeout))
|
|
return -1;
|
|
} else {
|
|
err = mmc_erase_t(mmc, start, blkcnt, arg);
|
|
|
|
if (err)
|
|
return -1;
|
|
|
|
/* Waiting for the ready status */
|
|
if (mmc_send_status(mmc, timeout))
|
|
return -1;
|
|
|
|
arg = MMC_SECURE_TRIM2_ARG;
|
|
err = mmc_erase_t(mmc, start, blkcnt, arg);
|
|
|
|
if (err)
|
|
return -1;
|
|
|
|
/* Waiting for the ready status */
|
|
if (mmc_send_status(mmc, timeout)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return blkcnt;
|
|
}
|
|
|
|
return blk;
|
|
}
|
|
|
|
static ulong mmc_write_blocks(struct mmc *mmc, lbaint_t start,
|
|
lbaint_t blkcnt, const void *src)
|
|
{
|
|
struct mmc_cmd cmd;
|
|
struct mmc_data data;
|
|
int timeout = 1000;
|
|
int ret = blkcnt;
|
|
|
|
if ((start + blkcnt) > mmc->block_dev.lba) {
|
|
printf("MMC: block number 0x" LBAF " exceeds max(0x" LBAF ")\n",
|
|
start + blkcnt, mmc->block_dev.lba);
|
|
return 0;
|
|
}
|
|
|
|
if (blkcnt == 0)
|
|
return 0;
|
|
else if (blkcnt == 1)
|
|
cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK;
|
|
else
|
|
cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK;
|
|
|
|
if (mmc->high_capacity)
|
|
cmd.cmdarg = start;
|
|
else
|
|
cmd.cmdarg = start * mmc->write_bl_len;
|
|
|
|
cmd.resp_type = MMC_RSP_R1;
|
|
|
|
data.src = src;
|
|
data.blocks = blkcnt;
|
|
data.blocksize = mmc->write_bl_len;
|
|
data.flags = MMC_DATA_WRITE;
|
|
|
|
if (mmc_send_cmd(mmc, &cmd, &data)) {
|
|
printf("mmc write failed\n");
|
|
ret = 0;
|
|
}
|
|
|
|
/* SPI multiblock writes terminate using a special
|
|
* token, not a STOP_TRANSMISSION request.
|
|
*/
|
|
if (!mmc_host_is_spi(mmc) && blkcnt > 1) {
|
|
cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
|
|
cmd.cmdarg = 0;
|
|
cmd.resp_type = MMC_RSP_R1b;
|
|
if (mmc_send_cmd(mmc, &cmd, NULL)) {
|
|
printf("mmc fail to send stop cmd\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Waiting for the ready status */
|
|
if (mmc_send_status(mmc, timeout))
|
|
return 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
ulong mmc_bwrite(int dev_num, lbaint_t start, lbaint_t blkcnt, const void *src)
|
|
{
|
|
lbaint_t cur, blocks_todo = blkcnt;
|
|
|
|
struct mmc *mmc = find_mmc_device(dev_num);
|
|
if (!mmc)
|
|
return 0;
|
|
|
|
if (mmc_set_blocklen(mmc, mmc->write_bl_len))
|
|
return 0;
|
|
|
|
#if !defined(CONFIG_SYS_DCACHE_OFF)
|
|
flush_cache((unsigned long)src,
|
|
(unsigned long)blkcnt * mmc->write_bl_len);
|
|
#endif
|
|
do {
|
|
cur = (blocks_todo > mmc->cfg->b_max) ?
|
|
mmc->cfg->b_max : blocks_todo;
|
|
if (mmc_write_blocks(mmc, start, cur, src) != cur)
|
|
return 0;
|
|
blocks_todo -= cur;
|
|
start += cur;
|
|
src += cur * mmc->write_bl_len;
|
|
} while (blocks_todo > 0);
|
|
|
|
return blkcnt;
|
|
}
|