mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 19:47:39 -04:00
mmc: sdhci-of-k1: add comprehensive SDR tuning support
Implement software tuning algorithm to enable UHS-I SDR modes for SD card operation and HS200 mode for eMMC. This adds both TX and RX delay line tuning based on the SpacemiT K1 controller capabilities. Algorithm features: - Add tuning register definitions (RX_CFG, DLINE_CTRL, DLINE_CFG) - Conditional tuning: only for high-speed modes (≥100MHz) - TX tuning: configure transmit delay line with optimal values (dline_reg=0, delaycode=127) to ensure optimal signal output timing - RX tuning: single-pass window detection algorithm testing full delay range (0-255) to find optimal receive timing window - Retry mechanism: multiple fallback delays within optimal window for improved reliability Tested-by: Anand Moon <linux.amoon@gmail.com> Acked-by: Adrian Hunter <adrian.hunter@intel.com> Tested-by: Trevor Gamblin <tgamblin@baylibre.com> Tested-by: Vincent Legoll <legoll@online.fr> Signed-off-by: Iker Pedrosa <ikerpedrosam@gmail.com> Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
committed by
Ulf Hansson
parent
00a97fc57c
commit
e9cb83c100
@@ -69,6 +69,28 @@
|
||||
#define SDHC_PHY_DRIVE_SEL GENMASK(2, 0)
|
||||
#define SDHC_RX_BIAS_CTRL BIT(5)
|
||||
|
||||
#define SPACEMIT_SDHC_RX_CFG_REG 0x118
|
||||
#define SDHC_RX_SDCLK_SEL0_MASK GENMASK(1, 0)
|
||||
#define SDHC_RX_SDCLK_SEL1_MASK GENMASK(3, 2)
|
||||
#define SDHC_RX_SDCLK_SEL1 FIELD_PREP(SDHC_RX_SDCLK_SEL1_MASK, 1)
|
||||
|
||||
#define SPACEMIT_SDHC_DLINE_CTRL_REG 0x130
|
||||
#define SDHC_DLINE_PU BIT(0)
|
||||
#define SDHC_RX_DLINE_CODE_MASK GENMASK(23, 16)
|
||||
#define SDHC_TX_DLINE_CODE_MASK GENMASK(31, 24)
|
||||
|
||||
#define SPACEMIT_SDHC_DLINE_CFG_REG 0x134
|
||||
#define SDHC_RX_DLINE_REG_MASK GENMASK(7, 0)
|
||||
#define SDHC_RX_DLINE_GAIN BIT(8)
|
||||
#define SDHC_TX_DLINE_REG_MASK GENMASK(23, 16)
|
||||
|
||||
#define SPACEMIT_RX_DLINE_REG 9
|
||||
#define SPACEMIT_RX_TUNE_DELAY_MIN 0x0
|
||||
#define SPACEMIT_RX_TUNE_DELAY_MAX 0xFF
|
||||
|
||||
#define SPACEMIT_TX_TUNING_DLINE_REG 0x00
|
||||
#define SPACEMIT_TX_TUNING_DELAYCODE 127
|
||||
|
||||
struct spacemit_sdhci_host {
|
||||
struct clk *clk_core;
|
||||
struct clk *clk_io;
|
||||
@@ -96,6 +118,50 @@ static inline void spacemit_sdhci_clrsetbits(struct sdhci_host *host, u32 clr, u
|
||||
sdhci_writel(host, val, reg);
|
||||
}
|
||||
|
||||
static void spacemit_sdhci_set_rx_delay(struct sdhci_host *host, u8 delay)
|
||||
{
|
||||
spacemit_sdhci_clrsetbits(host, SDHC_RX_DLINE_CODE_MASK,
|
||||
FIELD_PREP(SDHC_RX_DLINE_CODE_MASK, delay),
|
||||
SPACEMIT_SDHC_DLINE_CTRL_REG);
|
||||
}
|
||||
|
||||
static void spacemit_sdhci_set_tx_delay(struct sdhci_host *host, u8 delay)
|
||||
{
|
||||
spacemit_sdhci_clrsetbits(host, SDHC_TX_DLINE_CODE_MASK,
|
||||
FIELD_PREP(SDHC_TX_DLINE_CODE_MASK, delay),
|
||||
SPACEMIT_SDHC_DLINE_CTRL_REG);
|
||||
}
|
||||
|
||||
static void spacemit_sdhci_set_tx_dline_reg(struct sdhci_host *host, u8 dline_reg)
|
||||
{
|
||||
spacemit_sdhci_clrsetbits(host, SDHC_TX_DLINE_REG_MASK,
|
||||
FIELD_PREP(SDHC_TX_DLINE_REG_MASK, dline_reg),
|
||||
SPACEMIT_SDHC_DLINE_CFG_REG);
|
||||
}
|
||||
|
||||
static void spacemit_sdhci_tx_tuning_prepare(struct sdhci_host *host)
|
||||
{
|
||||
spacemit_sdhci_setbits(host, SDHC_TX_MUX_SEL, SPACEMIT_SDHC_TX_CFG_REG);
|
||||
spacemit_sdhci_setbits(host, SDHC_DLINE_PU, SPACEMIT_SDHC_DLINE_CTRL_REG);
|
||||
udelay(5);
|
||||
}
|
||||
|
||||
static void spacemit_sdhci_prepare_tuning(struct sdhci_host *host)
|
||||
{
|
||||
spacemit_sdhci_clrsetbits(host, SDHC_RX_DLINE_REG_MASK,
|
||||
FIELD_PREP(SDHC_RX_DLINE_REG_MASK, SPACEMIT_RX_DLINE_REG),
|
||||
SPACEMIT_SDHC_DLINE_CFG_REG);
|
||||
|
||||
spacemit_sdhci_setbits(host, SDHC_DLINE_PU, SPACEMIT_SDHC_DLINE_CTRL_REG);
|
||||
udelay(5);
|
||||
|
||||
spacemit_sdhci_clrsetbits(host, SDHC_RX_SDCLK_SEL1_MASK, SDHC_RX_SDCLK_SEL1,
|
||||
SPACEMIT_SDHC_RX_CFG_REG);
|
||||
|
||||
if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200)
|
||||
spacemit_sdhci_setbits(host, SDHC_HS200_USE_RFIFO, SPACEMIT_SDHC_PHY_FUNC_REG);
|
||||
}
|
||||
|
||||
static void spacemit_sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
sdhci_reset(host, mask);
|
||||
@@ -191,6 +257,111 @@ static unsigned int spacemit_sdhci_clk_get_max_clock(struct sdhci_host *host)
|
||||
return clk_get_rate(pltfm_host->clk);
|
||||
}
|
||||
|
||||
static int spacemit_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
{
|
||||
int current_len = 0, current_start = 0;
|
||||
int max_pass_len = 0, max_pass_start = 0;
|
||||
struct mmc_host *mmc = host->mmc;
|
||||
struct mmc_ios ios = mmc->ios;
|
||||
u8 final_delay;
|
||||
int ret = 0;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Tuning is required for SDR50/SDR104, HS200/HS400 cards and
|
||||
* if clock frequency is greater than 100MHz in these modes.
|
||||
*/
|
||||
if (host->clock < 100 * 1000 * 1000 ||
|
||||
!(ios.timing == MMC_TIMING_MMC_HS200 ||
|
||||
ios.timing == MMC_TIMING_UHS_SDR50 ||
|
||||
ios.timing == MMC_TIMING_UHS_SDR104))
|
||||
return 0;
|
||||
|
||||
if (mmc->caps2 & MMC_CAP2_NO_MMC) {
|
||||
spacemit_sdhci_set_tx_dline_reg(host, SPACEMIT_TX_TUNING_DLINE_REG);
|
||||
spacemit_sdhci_set_tx_delay(host, SPACEMIT_TX_TUNING_DELAYCODE);
|
||||
spacemit_sdhci_tx_tuning_prepare(host);
|
||||
|
||||
dev_dbg(mmc_dev(host->mmc), "TX tuning: dline_reg=%d, delaycode=%d\n",
|
||||
SPACEMIT_TX_TUNING_DLINE_REG, SPACEMIT_TX_TUNING_DELAYCODE);
|
||||
}
|
||||
|
||||
spacemit_sdhci_prepare_tuning(host);
|
||||
|
||||
for (i = SPACEMIT_RX_TUNE_DELAY_MIN; i <= SPACEMIT_RX_TUNE_DELAY_MAX; i++) {
|
||||
spacemit_sdhci_set_rx_delay(host, i);
|
||||
ret = mmc_send_tuning(host->mmc, opcode, NULL);
|
||||
|
||||
dev_dbg(mmc_dev(host->mmc), "RX delay %d: %s\n",
|
||||
i, ret == 0 ? "pass" : "fail");
|
||||
|
||||
if (ret == 0) {
|
||||
/* Test passed - extend current window */
|
||||
if (current_len == 0)
|
||||
current_start = i;
|
||||
current_len++;
|
||||
} else {
|
||||
/* Test failed - check if current window is best so far */
|
||||
if (current_len > max_pass_len) {
|
||||
max_pass_len = current_len;
|
||||
max_pass_start = current_start;
|
||||
}
|
||||
current_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_len > max_pass_len) {
|
||||
max_pass_len = current_len;
|
||||
max_pass_start = current_start;
|
||||
}
|
||||
|
||||
if (max_pass_len < 3) {
|
||||
dev_err(mmc_dev(host->mmc), "Tuning failed: no stable window found\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
final_delay = max_pass_start + max_pass_len / 2;
|
||||
spacemit_sdhci_set_rx_delay(host, final_delay);
|
||||
ret = mmc_send_tuning(host->mmc, opcode, NULL);
|
||||
if (ret) {
|
||||
u8 retry_delays[] = {
|
||||
max_pass_start + max_pass_len / 4,
|
||||
max_pass_start + (3 * max_pass_len) / 4,
|
||||
max_pass_start,
|
||||
max_pass_start + max_pass_len - 1
|
||||
};
|
||||
int retry_count = ARRAY_SIZE(retry_delays);
|
||||
|
||||
dev_warn(mmc_dev(mmc), "Primary delay %d failed, trying alternatives\n",
|
||||
final_delay);
|
||||
|
||||
for (i = 0; i < retry_count; i++) {
|
||||
if (retry_delays[i] >= SPACEMIT_RX_TUNE_DELAY_MIN &&
|
||||
retry_delays[i] <= SPACEMIT_RX_TUNE_DELAY_MAX) {
|
||||
spacemit_sdhci_set_rx_delay(host, retry_delays[i]);
|
||||
ret = mmc_send_tuning(host->mmc, opcode, NULL);
|
||||
if (!ret) {
|
||||
final_delay = retry_delays[i];
|
||||
dev_info(mmc_dev(mmc), "Retry successful with delay %d\n",
|
||||
final_delay);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
dev_err(mmc_dev(mmc), "All retry attempts failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
dev_dbg(mmc_dev(host->mmc),
|
||||
"Tuning successful: window %d-%d, using delay %d\n",
|
||||
max_pass_start, max_pass_start + max_pass_len - 1, final_delay);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spacemit_sdhci_pre_select_hs400(struct mmc_host *mmc)
|
||||
{
|
||||
struct sdhci_host *host = mmc_priv(mmc);
|
||||
@@ -326,6 +497,7 @@ static const struct sdhci_ops spacemit_sdhci_ops = {
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.set_clock = spacemit_sdhci_set_clock,
|
||||
.set_uhs_signaling = spacemit_sdhci_set_uhs_signaling,
|
||||
.platform_execute_tuning = spacemit_sdhci_execute_tuning,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data spacemit_sdhci_k1_pdata = {
|
||||
|
||||
Reference in New Issue
Block a user