Squashed backup commit (no shallow-clone history issues) of xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2 branch, upstream commit 9eece662e) plus all fixes applied to get everpal_defconfig building clean against a modern (GCC 15.2.0) toolchain -- see the previous commit message on the lineage-23.2 branch (also in this repo history is not available here since this is a squashed snapshot; full incremental history lives in the local DL380 checkout and the upstream GitHub remote). Upstream: https://github.com/xiaomi-mt6833-dev/kernel_xiaomi_mt6833 (lineage-23.2)
392 lines
9.1 KiB
C
392 lines
9.1 KiB
C
/*
|
|
* * ffu.c
|
|
*
|
|
* Copyright 2007-2008 Pierre Ossman
|
|
*
|
|
* Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
|
|
* slab.h, ffu.h & swap.h header files
|
|
* The original, unmodified version of this program - the mmc_test.c
|
|
* file - is obtained under the GPL v2.0 license that is available via
|
|
* http://www.gnu.org/licenses/,
|
|
* or http://www.opensource.org/licenses/gpl-2.0.php
|
|
*/
|
|
|
|
#include <linux/bug.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/host.h>
|
|
#include <linux/mmc/mmc.h>
|
|
#include <linux/mmc/ffu.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "../core/core.h"
|
|
#include "../core/mmc_ops.h"
|
|
|
|
|
|
#define FFU_BUS_FREQ 25000000
|
|
/*
|
|
* Turn the cache ON/OFF.
|
|
* Turning the cache OFF shall trigger flushing of the data
|
|
* to the non-volatile storage.
|
|
* This function should be called with host claimed
|
|
*/
|
|
int mmc_ffu_cache_ctrl(struct mmc_host *host, u8 enable)
|
|
{
|
|
struct mmc_card *card = host->card;
|
|
unsigned int timeout;
|
|
int err = 0;
|
|
|
|
if (card && mmc_card_mmc(card) &&
|
|
(card->ext_csd.cache_size > 0)) {
|
|
enable = !!enable;
|
|
|
|
if (card->ext_csd.cache_ctrl ^ enable) {
|
|
timeout = enable ? card->ext_csd.generic_cmd6_time : 0;
|
|
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
|
EXT_CSD_CACHE_CTRL, enable, timeout);
|
|
if (err)
|
|
pr_notice("%s: cache %s error %d\n",
|
|
mmc_hostname(card->host),
|
|
enable ? "on" : "off",
|
|
err);
|
|
else
|
|
card->ext_csd.cache_ctrl = enable;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Checks that a normal transfer didn't have any errors
|
|
*/
|
|
static int mmc_ffu_check_result(struct mmc_request *mrq)
|
|
{
|
|
if (!mrq || !mrq->cmd || !mrq->data)
|
|
return -EINVAL;
|
|
|
|
if (mrq->cmd->error != 0)
|
|
return -EINVAL;
|
|
|
|
if (mrq->data->error != 0)
|
|
return -EINVAL;
|
|
|
|
if (mrq->stop != NULL && mrq->stop->error != 0)
|
|
return -1;
|
|
|
|
if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_ffu_busy(struct mmc_command *cmd)
|
|
{
|
|
return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
|
|
(R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG);
|
|
}
|
|
|
|
static int mmc_ffu_wait_busy(struct mmc_card *card)
|
|
{
|
|
int ret, busy = 0;
|
|
struct mmc_command cmd = {0};
|
|
|
|
memset(&cmd, 0, sizeof(struct mmc_command));
|
|
cmd.opcode = MMC_SEND_STATUS;
|
|
cmd.arg = card->rca << 16;
|
|
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
|
|
|
|
do {
|
|
ret = mmc_wait_for_cmd(card->host, &cmd, 0);
|
|
if (ret)
|
|
break;
|
|
|
|
if (!busy && mmc_ffu_busy(&cmd)) {
|
|
busy = 1;
|
|
if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
|
|
pr_notice("%s: Warning: Host did not wait for busy state to end.\n",
|
|
mmc_hostname(card->host));
|
|
}
|
|
}
|
|
|
|
} while (mmc_ffu_busy(&cmd));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mmc_ffu_restart(struct mmc_card *card)
|
|
{
|
|
struct mmc_host *host = card->host;
|
|
int err = 0;
|
|
|
|
card->host->ios.timing = MMC_TIMING_LEGACY;
|
|
mmc_set_clock(card->host, 300000);
|
|
mmc_set_bus_width(card->host, MMC_BUS_WIDTH_1);
|
|
|
|
card->state |= MMC_STATE_FFUED;
|
|
|
|
err = mmc_reinit_oldcard(host);
|
|
pr_notice("mmc_init_card ret %d\n", err);
|
|
if (!err)
|
|
card->state &= ~MMC_STATE_FFUED;
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Host set the EXT_CSD */
|
|
static int mmc_host_set_ffu(struct mmc_card *card, u32 ffu_enable)
|
|
{
|
|
int err;
|
|
|
|
/* check if card is eMMC 5.0 or higher */
|
|
if (card->ext_csd.rev < 7)
|
|
return -EINVAL;
|
|
|
|
if (FFU_ENABLED(ffu_enable)) {
|
|
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
|
EXT_CSD_FW_CONFIG, MMC_FFU_ENABLE,
|
|
card->ext_csd.generic_cmd6_time);
|
|
if (err) {
|
|
pr_notice("%s: switch to FFU failed with error %d\n",
|
|
mmc_hostname(card->host), err);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define CID_MANFID_SANDISK 0x2
|
|
#define CID_MANFID_TOSHIBA 0x11
|
|
#define CID_MANFID_MICRON 0x13
|
|
#define CID_MANFID_SAMSUNG 0x15
|
|
#define CID_MANFID_SANDISK_NEW 0x45
|
|
#define CID_MANFID_KSI 0x70
|
|
#define CID_MANFID_HYNIX 0x90
|
|
|
|
int mmc_ffu_install(struct mmc_card *card, u8 *ext_csd)
|
|
{
|
|
u8 *ext_csd_new = NULL;
|
|
int err;
|
|
#if !defined(FFU_DUMMY_TEST)
|
|
u32 ffu_data_len;
|
|
u32 timeout;
|
|
u8 set = 1;
|
|
#endif
|
|
u8 retry = 10;
|
|
|
|
#if !defined(FFU_DUMMY_TEST)
|
|
if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
|
|
|
|
/* host switch back to work in normal MMC Read/Write commands */
|
|
if ((card->cid.manfid == CID_MANFID_HYNIX) &&
|
|
(card->cid.prv == 0x03)) {
|
|
set = 0;
|
|
}
|
|
|
|
pr_notice("FFU exit FFU mode\n");
|
|
err = mmc_switch(card, set,
|
|
EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
|
|
card->ext_csd.generic_cmd6_time);
|
|
if (err) {
|
|
pr_notice("FFU: %s: error %d exit FFU mode\n",
|
|
mmc_hostname(card->host), err);
|
|
mmc_ffu_restart(card);
|
|
goto exit;
|
|
}
|
|
|
|
}
|
|
|
|
/* check mode operation */
|
|
if (FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
|
|
ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
|
|
ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
|
|
ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
|
|
ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
|
|
|
|
if (!ffu_data_len) {
|
|
err = -EPERM;
|
|
mmc_ffu_restart(card);
|
|
return err;
|
|
}
|
|
|
|
timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
|
|
if (timeout == 0 || timeout > 0x17) {
|
|
timeout = 0x17;
|
|
pr_notice("FFU: %s: operation code timeout is out of range. Using maximum timeout.\n",
|
|
mmc_hostname(card->host));
|
|
}
|
|
|
|
/* timeout is at millisecond resolution */
|
|
timeout = (100 * (1 << timeout) / 1000) + 1;
|
|
|
|
/* set ext_csd to install mode */
|
|
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
|
EXT_CSD_MODE_OPERATION_CODES,
|
|
MMC_FFU_INSTALL_SET, timeout);
|
|
|
|
if (err) {
|
|
pr_notice("FFU: %s: error %d setting install mode\n",
|
|
mmc_hostname(card->host), err);
|
|
mmc_ffu_restart(card);
|
|
goto exit;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
err = mmc_ffu_restart(card);
|
|
if (err) {
|
|
pr_notice("FFU: %s: error %d restart\n",
|
|
mmc_hostname(card->host), err);
|
|
goto exit;
|
|
}
|
|
#ifdef CONFIG_MTK_EMMC_HW_CQ
|
|
if (card)
|
|
mmc_cmdq_disable(card);
|
|
#endif
|
|
/* read ext_csd */
|
|
while (retry--) {
|
|
err = mmc_get_ext_csd(card, &ext_csd_new);
|
|
if (err)
|
|
pr_notice("FFU: %s: sending ext_csd retry times %d\n",
|
|
mmc_hostname(card->host), retry);
|
|
else
|
|
break;
|
|
}
|
|
#ifdef CONFIG_MTK_EMMC_HW_CQ
|
|
if (card)
|
|
mmc_cmdq_enable(card);
|
|
#endif
|
|
if (err) {
|
|
pr_notice("FFU: %s: sending ext_csd error %d\n",
|
|
mmc_hostname(card->host), err);
|
|
goto exit;
|
|
}
|
|
|
|
/* return status */
|
|
err = ext_csd_new[EXT_CSD_FFU_STATUS];
|
|
if (!err) {
|
|
pr_notice("FFU: %s: succeed FFU\n",
|
|
mmc_hostname(card->host));
|
|
} else if (err) {
|
|
pr_notice("FFU: %s: error %d FFU install:\n",
|
|
mmc_hostname(card->host), err);
|
|
err = -EINVAL;
|
|
}
|
|
|
|
exit:
|
|
kfree(ext_csd_new);
|
|
return err;
|
|
}
|
|
|
|
void mmc_wait_for_ffu_req(struct mmc_host *host, struct mmc_request *mrq)
|
|
{
|
|
struct mmc_card *card = host->card;
|
|
struct mmc_command *cmd = mrq->cmd;
|
|
#ifndef FFU_DUMMY_TEST
|
|
struct mmc_command stop = {0};
|
|
#endif
|
|
u8 *ext_csd = NULL;
|
|
int err;
|
|
|
|
if (!mmc_card_mmc(card)) {
|
|
mrq->cmd->error = -EINVAL;
|
|
return;
|
|
} else if (cmd->opcode == MMC_FFU_INSTALL_OP) {
|
|
/*
|
|
* Always return success since we installed when
|
|
* MMC_FFU_DOWNLOAD_OP
|
|
*/
|
|
return;
|
|
}
|
|
/* Read EXT_CSD */
|
|
err = mmc_get_ext_csd(card, &ext_csd);
|
|
if (err) {
|
|
pr_notice("FFU: %s: error %d sending ext_csd\n",
|
|
mmc_hostname(card->host), err);
|
|
goto exit;
|
|
}
|
|
|
|
/* Check if FFU is supported by card */
|
|
if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE])) {
|
|
err = -EINVAL;
|
|
pr_notice("FFU: %s: error %d FFU is not supported\n",
|
|
mmc_hostname(card->host), err);
|
|
goto exit;
|
|
}
|
|
|
|
err = mmc_host_set_ffu(card, ext_csd[EXT_CSD_FW_CONFIG]);
|
|
if (err) {
|
|
pr_notice("FFU: %s: error %d FFU fails to enable\n",
|
|
mmc_hostname(card->host), err);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
pr_notice("eMMC cache originally %s -> %s\n",
|
|
((card->ext_csd.cache_ctrl) ? "on" : "off"),
|
|
((card->ext_csd.cache_ctrl) ? "turn off" : "keep"));
|
|
if (card->ext_csd.cache_ctrl) {
|
|
mmc_flush_cache(card);
|
|
mmc_ffu_cache_ctrl(card->host, 0);
|
|
}
|
|
|
|
/* set device to FFU mode */
|
|
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
|
|
EXT_CSD_MODE_CONFIG,
|
|
MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
|
|
if (err) {
|
|
pr_notice("FFU: %s: error %d when switch to FFU\n",
|
|
mmc_hostname(card->host), err);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
#ifndef FFU_DUMMY_TEST
|
|
/* set CMD ARG */
|
|
cmd->arg = ext_csd[EXT_CSD_FFU_ARG] |
|
|
ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
|
|
ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
|
|
ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
|
|
|
|
/* If arg is zero, should be set to a special value for samsung
|
|
*/
|
|
if (card->cid.manfid == CID_MANFID_SAMSUNG && cmd->arg == 0x0)
|
|
cmd->arg = 0xc7810000;
|
|
|
|
pr_notice("FFU perform write\n");
|
|
|
|
//FFU_ARG, DATA_SECTOR_SIZE
|
|
mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
|
|
mrq->stop = &stop;
|
|
mrq->stop->opcode = MMC_STOP_TRANSMISSION;
|
|
mrq->stop->arg = 0;
|
|
mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
|
|
|
|
mmc_wait_for_req(card->host, mrq);
|
|
mmc_ffu_wait_busy(card);
|
|
|
|
err = mmc_ffu_check_result(mrq);
|
|
if (err) {
|
|
pr_notice("FFU: %s: error %d write fail\n",
|
|
mmc_hostname(card->host), err);
|
|
goto exit;
|
|
}
|
|
|
|
#endif
|
|
err = mmc_ffu_install(card, ext_csd);
|
|
|
|
exit:
|
|
mrq->cmd->error = err;
|
|
kfree(ext_csd);
|
|
}
|
|
EXPORT_SYMBOL(mmc_wait_for_ffu_req);
|