Skip to content

Commit

Permalink
mmc: mediatek: Use data tune for CMD line tune
Browse files Browse the repository at this point in the history
If we don't select a set of better parameters for our emmc host,
It may easily occur CMD response CRC error. And also it may cause
cannot boot up issue.

Fot getting a set of better parameters, our emmc host supports
data tune mechanism.Therefore, our emmc driver also should change
to use data tune for CMD line.

Because our emmc host use the different clock source to sample the
CMD signal between HS200 and HS400 mode, the parameters are also
different between these two modes.
Separate cmd internal delay for HS200/HS400 mode.

This change can fix "System can not boot up" issue.

Signed-off-by: Yong Mao <yong.mao@mediatek.com>
Signed-off-by: Chaotian Jing <chaotian.jing@mediatek.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
  • Loading branch information
yong mao authored and Ulf Hansson committed Apr 24, 2017
1 parent 50fde56 commit 1ede5cb
Showing 1 changed file with 152 additions and 16 deletions.
168 changes: 152 additions & 16 deletions drivers/mmc/host/mtk-sd.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
#define MSDC_PATCH_BIT1 0xb4
#define MSDC_PAD_TUNE 0xec
#define PAD_DS_TUNE 0x188
#define PAD_CMD_TUNE 0x18c
#define EMMC50_CFG0 0x208

/*--------------------------------------------------------------------------*/
Expand Down Expand Up @@ -211,13 +212,18 @@
#define MSDC_PATCH_BIT_SPCPUSH (0x1 << 29) /* RW */
#define MSDC_PATCH_BIT_DECRCTMO (0x1 << 30) /* RW */

#define MSDC_PAD_TUNE_DATWRDLY (0x1f << 0) /* RW */
#define MSDC_PAD_TUNE_DATRRDLY (0x1f << 8) /* RW */
#define MSDC_PAD_TUNE_CMDRDLY (0x1f << 16) /* RW */
#define MSDC_PAD_TUNE_CMDRRDLY (0x1f << 22) /* RW */
#define MSDC_PAD_TUNE_CLKTDLY (0x1f << 27) /* RW */

#define PAD_DS_TUNE_DLY1 (0x1f << 2) /* RW */
#define PAD_DS_TUNE_DLY2 (0x1f << 7) /* RW */
#define PAD_DS_TUNE_DLY3 (0x1f << 12) /* RW */

#define PAD_CMD_TUNE_RX_DLY3 (0x1f << 1) /* RW */

#define EMMC50_CFG_PADCMD_LATCHCK (0x1 << 0) /* RW */
#define EMMC50_CFG_CRCSTS_EDGE (0x1 << 3) /* RW */
#define EMMC50_CFG_CFCSTS_SEL (0x1 << 4) /* RW */
Expand Down Expand Up @@ -285,12 +291,14 @@ struct msdc_save_para {
u32 patch_bit0;
u32 patch_bit1;
u32 pad_ds_tune;
u32 pad_cmd_tune;
u32 emmc50_cfg0;
};

struct msdc_tune_para {
u32 iocon;
u32 pad_tune;
u32 pad_cmd_tune;
};

struct msdc_delay_phase {
Expand Down Expand Up @@ -332,6 +340,10 @@ struct msdc_host {
unsigned char timing;
bool vqmmc_enabled;
u32 hs400_ds_delay;
u32 hs200_cmd_int_delay; /* cmd internal delay for HS200/SDR104 */
u32 hs400_cmd_int_delay; /* cmd internal delay for HS400 */
bool hs400_cmd_resp_sel_rising;
/* cmd response sample selection for HS400 */
bool hs400_mode; /* current eMMC will run at hs400 mode */
struct msdc_save_para save_para; /* used when gate HCLK */
struct msdc_tune_para def_tune_para; /* default tune setting */
Expand Down Expand Up @@ -601,8 +613,14 @@ static void msdc_set_mclk(struct msdc_host *host, unsigned char timing, u32 hz)
} else {
writel(host->saved_tune_para.iocon, host->base + MSDC_IOCON);
writel(host->saved_tune_para.pad_tune, host->base + MSDC_PAD_TUNE);
writel(host->saved_tune_para.pad_cmd_tune,
host->base + PAD_CMD_TUNE);
}

if (timing == MMC_TIMING_MMC_HS400)
sdr_set_field(host->base + PAD_CMD_TUNE,
MSDC_PAD_TUNE_CMDRRDLY,
host->hs400_cmd_int_delay);
dev_dbg(host->dev, "sclk: %d, timing: %d\n", host->sclk, timing);
}

Expand Down Expand Up @@ -1303,7 +1321,7 @@ static struct msdc_delay_phase get_best_delay(struct msdc_host *host, u32 delay)
len_final = len;
}
start += len ? len : 1;
if (len >= 8 && start_final < 4)
if (len >= 12 && start_final < 4)
break;
}

Expand All @@ -1326,36 +1344,67 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
struct msdc_host *host = mmc_priv(mmc);
u32 rise_delay = 0, fall_delay = 0;
struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0,};
struct msdc_delay_phase internal_delay_phase;
u8 final_delay, final_maxlen;
u32 internal_delay = 0;
int cmd_err;
int i;
int i, j;

if (mmc->ios.timing == MMC_TIMING_MMC_HS200 ||
mmc->ios.timing == MMC_TIMING_UHS_SDR104)
sdr_set_field(host->base + MSDC_PAD_TUNE,
MSDC_PAD_TUNE_CMDRRDLY,
host->hs200_cmd_int_delay);

sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
sdr_set_field(host->base + MSDC_PAD_TUNE,
MSDC_PAD_TUNE_CMDRDLY, i);
mmc_send_tuning(mmc, opcode, &cmd_err);
if (!cmd_err)
rise_delay |= (1 << i);
/*
* Using the same parameters, it may sometimes pass the test,
* but sometimes it may fail. To make sure the parameters are
* more stable, we test each set of parameters 3 times.
*/
for (j = 0; j < 3; j++) {
mmc_send_tuning(mmc, opcode, &cmd_err);
if (!cmd_err) {
rise_delay |= (1 << i);
} else {
rise_delay &= ~(1 << i);
break;
}
}
}
final_rise_delay = get_best_delay(host, rise_delay);
/* if rising edge has enough margin, then do not scan falling edge */
if (final_rise_delay.maxlen >= 10 ||
(final_rise_delay.start == 0 && final_rise_delay.maxlen >= 4))
if (final_rise_delay.maxlen >= 12 && final_rise_delay.start < 4)
goto skip_fall;

sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
for (i = 0; i < PAD_DELAY_MAX; i++) {
sdr_set_field(host->base + MSDC_PAD_TUNE,
MSDC_PAD_TUNE_CMDRDLY, i);
mmc_send_tuning(mmc, opcode, &cmd_err);
if (!cmd_err)
fall_delay |= (1 << i);
/*
* Using the same parameters, it may sometimes pass the test,
* but sometimes it may fail. To make sure the parameters are
* more stable, we test each set of parameters 3 times.
*/
for (j = 0; j < 3; j++) {
mmc_send_tuning(mmc, opcode, &cmd_err);
if (!cmd_err) {
fall_delay |= (1 << i);
} else {
fall_delay &= ~(1 << i);
break;
}
}
}
final_fall_delay = get_best_delay(host, fall_delay);

skip_fall:
final_maxlen = max(final_rise_delay.maxlen, final_fall_delay.maxlen);
if (final_fall_delay.maxlen >= 12 && final_fall_delay.start < 4)
final_maxlen = final_fall_delay.maxlen;
if (final_maxlen == final_rise_delay.maxlen) {
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
sdr_set_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRDLY,
Expand All @@ -1367,7 +1416,71 @@ static int msdc_tune_response(struct mmc_host *mmc, u32 opcode)
final_fall_delay.final_phase);
final_delay = final_fall_delay.final_phase;
}
if (host->hs200_cmd_int_delay)
goto skip_internal;

for (i = 0; i < PAD_DELAY_MAX; i++) {
sdr_set_field(host->base + MSDC_PAD_TUNE,
MSDC_PAD_TUNE_CMDRRDLY, i);
mmc_send_tuning(mmc, opcode, &cmd_err);
if (!cmd_err)
internal_delay |= (1 << i);
}
dev_dbg(host->dev, "Final internal delay: 0x%x\n", internal_delay);
internal_delay_phase = get_best_delay(host, internal_delay);
sdr_set_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRRDLY,
internal_delay_phase.final_phase);
skip_internal:
dev_dbg(host->dev, "Final cmd pad delay: %x\n", final_delay);
return final_delay == 0xff ? -EIO : 0;
}

static int hs400_tune_response(struct mmc_host *mmc, u32 opcode)
{
struct msdc_host *host = mmc_priv(mmc);
u32 cmd_delay = 0;
struct msdc_delay_phase final_cmd_delay = { 0,};
u8 final_delay;
int cmd_err;
int i, j;

/* select EMMC50 PAD CMD tune */
sdr_set_bits(host->base + PAD_CMD_TUNE, BIT(0));

if (mmc->ios.timing == MMC_TIMING_MMC_HS200 ||
mmc->ios.timing == MMC_TIMING_UHS_SDR104)
sdr_set_field(host->base + MSDC_PAD_TUNE,
MSDC_PAD_TUNE_CMDRRDLY,
host->hs200_cmd_int_delay);

if (host->hs400_cmd_resp_sel_rising)
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
else
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
for (i = 0 ; i < PAD_DELAY_MAX; i++) {
sdr_set_field(host->base + PAD_CMD_TUNE,
PAD_CMD_TUNE_RX_DLY3, i);
/*
* Using the same parameters, it may sometimes pass the test,
* but sometimes it may fail. To make sure the parameters are
* more stable, we test each set of parameters 3 times.
*/
for (j = 0; j < 3; j++) {
mmc_send_tuning(mmc, opcode, &cmd_err);
if (!cmd_err) {
cmd_delay |= (1 << i);
} else {
cmd_delay &= ~(1 << i);
break;
}
}
}
final_cmd_delay = get_best_delay(host, cmd_delay);
sdr_set_field(host->base + PAD_CMD_TUNE, PAD_CMD_TUNE_RX_DLY3,
final_cmd_delay.final_phase);
final_delay = final_cmd_delay.final_phase;

dev_dbg(host->dev, "Final cmd pad delay: %x\n", final_delay);
return final_delay == 0xff ? -EIO : 0;
}

Expand All @@ -1390,7 +1503,7 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
}
final_rise_delay = get_best_delay(host, rise_delay);
/* if rising edge has enough margin, then do not scan falling edge */
if (final_rise_delay.maxlen >= 10 ||
if (final_rise_delay.maxlen >= 12 ||
(final_rise_delay.start == 0 && final_rise_delay.maxlen >= 4))
goto skip_fall;

Expand Down Expand Up @@ -1423,6 +1536,7 @@ static int msdc_tune_data(struct mmc_host *mmc, u32 opcode)
final_delay = final_fall_delay.final_phase;
}

dev_dbg(host->dev, "Final data pad delay: %x\n", final_delay);
return final_delay == 0xff ? -EIO : 0;
}

Expand All @@ -1431,7 +1545,10 @@ static int msdc_execute_tuning(struct mmc_host *mmc, u32 opcode)
struct msdc_host *host = mmc_priv(mmc);
int ret;

ret = msdc_tune_response(mmc, opcode);
if (host->hs400_mode)
ret = hs400_tune_response(mmc, opcode);
else
ret = msdc_tune_response(mmc, opcode);
if (ret == -EIO) {
dev_err(host->dev, "Tune response fail!\n");
return ret;
Expand All @@ -1444,6 +1561,7 @@ static int msdc_execute_tuning(struct mmc_host *mmc, u32 opcode)

host->saved_tune_para.iocon = readl(host->base + MSDC_IOCON);
host->saved_tune_para.pad_tune = readl(host->base + MSDC_PAD_TUNE);
host->saved_tune_para.pad_cmd_tune = readl(host->base + PAD_CMD_TUNE);
return ret;
}

Expand Down Expand Up @@ -1478,6 +1596,25 @@ static struct mmc_host_ops mt_msdc_ops = {
.hw_reset = msdc_hw_reset,
};

static void msdc_of_property_parse(struct platform_device *pdev,
struct msdc_host *host)
{
of_property_read_u32(pdev->dev.of_node, "hs400-ds-delay",
&host->hs400_ds_delay);

of_property_read_u32(pdev->dev.of_node, "mediatek,hs200-cmd-int-delay",
&host->hs200_cmd_int_delay);

of_property_read_u32(pdev->dev.of_node, "mediatek,hs400-cmd-int-delay",
&host->hs400_cmd_int_delay);

if (of_property_read_bool(pdev->dev.of_node,
"mediatek,hs400-cmd-resp-sel-rising"))
host->hs400_cmd_resp_sel_rising = true;
else
host->hs400_cmd_resp_sel_rising = false;
}

static int msdc_drv_probe(struct platform_device *pdev)
{
struct mmc_host *mmc;
Expand Down Expand Up @@ -1549,10 +1686,7 @@ static int msdc_drv_probe(struct platform_device *pdev)
goto host_free;
}

if (!of_property_read_u32(pdev->dev.of_node, "hs400-ds-delay",
&host->hs400_ds_delay))
dev_dbg(&pdev->dev, "hs400-ds-delay: %x\n",
host->hs400_ds_delay);
msdc_of_property_parse(pdev, host);

host->dev = &pdev->dev;
host->mmc = mmc;
Expand Down Expand Up @@ -1664,6 +1798,7 @@ static void msdc_save_reg(struct msdc_host *host)
host->save_para.patch_bit0 = readl(host->base + MSDC_PATCH_BIT);
host->save_para.patch_bit1 = readl(host->base + MSDC_PATCH_BIT1);
host->save_para.pad_ds_tune = readl(host->base + PAD_DS_TUNE);
host->save_para.pad_cmd_tune = readl(host->base + PAD_CMD_TUNE);
host->save_para.emmc50_cfg0 = readl(host->base + EMMC50_CFG0);
}

Expand All @@ -1676,6 +1811,7 @@ static void msdc_restore_reg(struct msdc_host *host)
writel(host->save_para.patch_bit0, host->base + MSDC_PATCH_BIT);
writel(host->save_para.patch_bit1, host->base + MSDC_PATCH_BIT1);
writel(host->save_para.pad_ds_tune, host->base + PAD_DS_TUNE);
writel(host->save_para.pad_cmd_tune, host->base + PAD_CMD_TUNE);
writel(host->save_para.emmc50_cfg0, host->base + EMMC50_CFG0);
}

Expand Down

0 comments on commit 1ede5cb

Please sign in to comment.