diff options
| -rw-r--r-- | drivers/mmc/sdhci-cadence.c | 108 | ||||
| -rw-r--r-- | drivers/mmc/sdhci-cadence.h | 5 | ||||
| -rw-r--r-- | drivers/mmc/sdhci-cadence6.c | 45 |
3 files changed, 156 insertions, 2 deletions
diff --git a/drivers/mmc/sdhci-cadence.c b/drivers/mmc/sdhci-cadence.c index 5bbc18dfa51..a76f9e8d6bd 100644 --- a/drivers/mmc/sdhci-cadence.c +++ b/drivers/mmc/sdhci-cadence.c @@ -39,6 +39,9 @@ static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, }; +static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev, + unsigned int opcode); + static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_plat *plat, u8 addr, u8 data) { @@ -155,8 +158,93 @@ static void sdhci_cdns_set_control_reg(struct sdhci_host *host) sdhci_cdns6_phy_adj(mmc->dev, plat, mmc->selected_mode); } +static __maybe_unused bool sdhci_cdns_sd_needs_tuning(struct mmc *mmc) +{ + struct sdhci_cdns_plat *plat = dev_get_plat(mmc->dev); + + if (!IS_SD(mmc)) + return false; + + if (!dev_read_bool(mmc->dev, "cdns,sd-hs-tuning")) + return false; + + /* Already tuned for this mode */ + if (plat->tuned_mode == mmc->selected_mode) + return false; + + switch (mmc->selected_mode) { + case SD_HS: + return mmc->bus_width == 4; + /* Add future modes here, e.g.: + * case UHS_SDR50: + * return true; + */ + default: + return false; + } +} + +static int sdhci_cdns_set_ios_post(struct sdhci_host *host) +{ + struct mmc *mmc = host->mmc; + struct sdhci_cdns_plat *plat = dev_get_plat(mmc->dev); + int ret __maybe_unused; + /* + * The SD6HC soft PHY requires runtime DLL delay calibration + * for SD High Speed mode. The default PHY_DLL_SLAVE_CTRL_REG + * values (READ_DQS_CMD_DELAY and READ_DQS_DELAY = 0) do not + * provide sufficient timing margin due to PVT and board trace + * variations. + * + * Tuning is performed once per entry into SD_HS mode + * (tracked by plat->tuned_mode state). The calibrated PHY delay + * values remain valid while the card stays in SD_HS mode, and + * leaving that tuned mode clears the state so re-entering SD_HS + * triggers tuning again. + * + * This must be done in set_ios_post (not set_control_reg) + * because the SDHCI controller must already be operating at + * the target bus width, clock, and speed mode before CMD19 + * tuning commands can succeed. + */ + + if (IS_ENABLED(CONFIG_MMC_SUPPORTS_TUNING)) { + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_420 && + sdhci_cdns_sd_needs_tuning(mmc)) { + ret = sdhci_cdns_execute_tuning(mmc->dev, + MMC_CMD_SEND_TUNING_BLOCK); + if (ret) { + dev_err(mmc->dev, + "SD_HS tuning failed (ret=%d), using default PHY\n", + ret); + /* Restore default PHY settings and avoid retrying in this mode */ + sdhci_cdns6_phy_adj(mmc->dev, plat, + mmc->selected_mode); + plat->tuned_mode = mmc->selected_mode; + plat->tuned_dll_slave_ctrl = sdhci_cdns6_phy_get_dll_slave(plat); + return 0; + } + /* + * Tuning succeeded. The tuned_mode is already set by + * execute_tuning(), so the tuned value will be preserved + * across subsequent PHY reconfigurations. + */ + dev_dbg(mmc->dev, "SD_HS tuning successful\n"); + } + + /* Reset when mode changes away from a tuned mode */ + if (mmc->selected_mode != plat->tuned_mode) { + plat->tuned_mode = MMC_MODES_END; + plat->tuned_dll_slave_ctrl = 0; + } + } + + return 0; +} + static const struct sdhci_ops sdhci_cdns_ops = { .set_control_reg = sdhci_cdns_set_control_reg, + .set_ios_post = sdhci_cdns_set_ios_post, }; static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat, @@ -204,6 +292,7 @@ static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev, int cur_streak = 0; int max_streak = 0; int end_of_streak = 0; + int ret; int i; /* @@ -229,7 +318,24 @@ static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev, return -EIO; } - return sdhci_cdns_set_tune_val(plat, end_of_streak - max_streak / 2); + ret = sdhci_cdns_set_tune_val(plat, end_of_streak - max_streak / 2); + if (ret) + return ret; + + /* + * Mark this mode as tuned. This is critical for both driver tuning + * (SD_HS via set_ios_post) and framework tuning (UHS_SDR104, MMC_HS_200, + * MMC_HS_400) so that subsequent PHY reconfigurations restore the + * calibrated DLL value instead of overwriting with DT defaults. + * + * For HS400, tuning is performed while the controller is in HS200 mode + * (mmc->selected_mode == MMC_HS_200 and mmc->hs400_tuning == true). + * Record the tuned mode as MMC_HS_400 so the calibrated DLL value is + * preserved across the HS200→HS400 transition. + */ + plat->tuned_mode = mmc->hs400_tuning ? MMC_HS_400 : mmc->selected_mode; + + return 0; } static struct dm_mmc_ops sdhci_cdns_mmc_ops; diff --git a/drivers/mmc/sdhci-cadence.h b/drivers/mmc/sdhci-cadence.h index 7101f00b75b..ea517491860 100644 --- a/drivers/mmc/sdhci-cadence.h +++ b/drivers/mmc/sdhci-cadence.h @@ -7,6 +7,8 @@ #ifndef SDHCI_CADENCE_H_ #define SDHCI_CADENCE_H_ +#include <mmc.h> + /* HRS - Host Register Set (specific to Cadence) */ /* PHY access port */ #define SDHCI_CDNS_HRS04 0x10 @@ -60,10 +62,13 @@ struct sdhci_cdns_plat { struct mmc_config cfg; struct mmc mmc; void __iomem *hrs_addr; + enum bus_mode tuned_mode; + u32 tuned_dll_slave_ctrl; }; int sdhci_cdns6_phy_adj(struct udevice *dev, struct sdhci_cdns_plat *plat, u32 mode); int sdhci_cdns6_phy_init(struct udevice *dev, struct sdhci_cdns_plat *plat); int sdhci_cdns6_set_tune_val(struct sdhci_cdns_plat *plat, unsigned int val); +u32 sdhci_cdns6_phy_get_dll_slave(struct sdhci_cdns_plat *plat); #endif diff --git a/drivers/mmc/sdhci-cadence6.c b/drivers/mmc/sdhci-cadence6.c index ca1086e2359..c8b42532e17 100644 --- a/drivers/mmc/sdhci-cadence6.c +++ b/drivers/mmc/sdhci-cadence6.c @@ -173,6 +173,30 @@ static void sdhci_cdns6_write_phy_reg(struct sdhci_cdns_plat *plat, u32 addr, u3 writel(val, plat->hrs_addr + SDHCI_CDNS_HRS05); } +static bool sdhci_cdns6_mode_is_tuned(struct sdhci_cdns_plat *plat, u32 mode) +{ + /* + * Check if the given mode has a valid tuned DLL value. + * Only modes that support tuning (driver or framework) can have + * valid tuned values. This prevents the initial state (tuned_mode=0) + * from falsely matching MMC_LEGACY. + */ + if (plat->tuned_mode != mode) + return false; + + switch (mode) { + case SD_HS: /* Driver tuning via set_ios_post */ + case UHS_SDR50: /* Future driver tuning support */ + case UHS_SDR104: /* Framework tuning */ + case MMC_HS_200: /* Framework tuning */ + case MMC_HS_400: /* Framework tuning */ + case MMC_HS_400_ES: /* Framework tuning */ + return true; + default: + return false; + } +} + static int sdhci_cdns6_reset_phy_dll(struct sdhci_cdns_plat *plat, bool reset) { void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS09; @@ -259,7 +283,18 @@ int sdhci_cdns6_phy_adj(struct udevice *dev, struct sdhci_cdns_plat *plat, u32 m sdhci_cdns6_write_phy_reg(plat, PHY_DQS_TIMING_REG_ADDR, sdhci_cdns6_phy_cfgs[0].val); sdhci_cdns6_write_phy_reg(plat, PHY_GATE_LPBK_CTRL_REG_ADDR, sdhci_cdns6_phy_cfgs[1].val); sdhci_cdns6_write_phy_reg(plat, PHY_DLL_MASTER_CTRL_REG_ADDR, sdhci_cdns6_phy_cfgs[4].val); - sdhci_cdns6_write_phy_reg(plat, PHY_DLL_SLAVE_CTRL_REG_ADDR, sdhci_cdns6_phy_cfgs[2].val); + if (sdhci_cdns6_mode_is_tuned(plat, mode)) { + /* + * Use previously saved tuned DLL slave control value. + * Note: 0 is a valid tuned value (e.g., optimal tap at position 0), + * so we check both mode match AND that it's a tunable mode. + */ + sdhci_cdns6_write_phy_reg(plat, PHY_DLL_SLAVE_CTRL_REG_ADDR, + plat->tuned_dll_slave_ctrl); + } else { + sdhci_cdns6_write_phy_reg(plat, PHY_DLL_SLAVE_CTRL_REG_ADDR, + sdhci_cdns6_phy_cfgs[2].val); + } /* Switch Off the DLL Reset */ ret = sdhci_cdns6_reset_phy_dll(plat, false); @@ -318,6 +353,9 @@ int sdhci_cdns6_set_tune_val(struct sdhci_cdns_plat *plat, unsigned int val) sdhci_cdns6_write_phy_reg(plat, PHY_DLL_SLAVE_CTRL_REG_ADDR, tmp); + /* Store tuned DLL slave control value which will be reapplied via set_ios(). */ + plat->tuned_dll_slave_ctrl = tmp; + /* Switch Off the DLL Reset */ ret = sdhci_cdns6_reset_phy_dll(plat, false); if (ret) { @@ -327,3 +365,8 @@ int sdhci_cdns6_set_tune_val(struct sdhci_cdns_plat *plat, unsigned int val) return 0; } + +u32 sdhci_cdns6_phy_get_dll_slave(struct sdhci_cdns_plat *plat) +{ + return sdhci_cdns6_read_phy_reg(plat, PHY_DLL_SLAVE_CTRL_REG_ADDR); +} |
