On 02/09/2026 19:05, Aswin Murugan wrote:
> The Qualcomm SDHCI driver lacked DLL init/calibration and tuning
> support, so it never negotiated above high-speed/DDR52. Add the
> CM_DLL init/phase-tuning sequence for HS200 and the SDC4 DLL
> calibration for HS400, matching the SDCC core generation variants
> already distinguished in this driver.

Please check this actually builds, you need to select a config option to
expose MMC tuning support, and ensure we can also build without it for SPL.

Thanks

> 
> Signed-off-by: Aswin Murugan <[email protected]>
> ---
>  drivers/mmc/msm_sdhci.c | 752 +++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 751 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/mmc/msm_sdhci.c b/drivers/mmc/msm_sdhci.c
> index e0753a1cd5d..668aadf6c6e 100644
> --- a/drivers/mmc/msm_sdhci.c
> +++ b/drivers/mmc/msm_sdhci.c
> @@ -15,7 +15,9 @@
>  #include <wait_bit.h>
>  #include <asm/io.h>
>  #include <linux/bitops.h>
> +#include <linux/iopoll.h>
>  #include <power/regulator.h>
> +#include <memalign.h>
>  
>  /* Non-standard registers needed for SDHCI startup */
>  #define SDCC_MCI_POWER   0x0
> @@ -38,6 +40,52 @@
>  #define CORE_DLL_PDN         BIT(29)
>  #define CORE_DLL_RST         BIT(30)
>  
> +/* DLL configuration */
> +#define CORE_DLL_EN          BIT(16)
> +#define CORE_CDR_EN          BIT(17)
> +#define CORE_CK_OUT_EN               BIT(18)
> +#define CORE_CDR_EXT_EN              BIT(19)
> +#define CORE_DLL_LOCK                BIT(7)
> +#define CORE_CMD_DAT_TRACK_SEL       BIT(0)
> +
> +#define CDR_SELEXT_SHIFT     20
> +#define CDR_SELEXT_MASK              (0xf << CDR_SELEXT_SHIFT)
> +
> +/* MCLK frequency selection (CMUX_SHIFT_PHASE) - required for the DLL to 
> lock */
> +#define CMUX_SHIFT_PHASE_SHIFT       24
> +#define CMUX_SHIFT_PHASE_MASK        (7 << CMUX_SHIFT_PHASE_SHIFT)
> +
> +/* 14LPP DLL reset / Tassadar DLL extras, for SDCC minor >= 0x42 / 0x71 */
> +#define CORE_DLL_CLOCK_DISABLE       BIT(21)
> +
> +#define DLL_USR_CTL_POR_VAL  0x10800
> +#define ENABLE_DLL_LOCK_STATUS       BIT(26)
> +#define FINE_TUNE_MODE_EN    BIT(27)
> +#define BIAS_OK_SIGNAL               BIT(29)
> +
> +#define DLL_CONFIG_3_LOW_FREQ_VAL    0x08
> +#define DLL_CONFIG_3_HIGH_FREQ_VAL   0x10
> +
> +#define CORE_CLK_PWRSAVE     BIT(1)
> +
> +/* Timing mode selection */
> +#define CORE_HC_MCLK_SEL_DFLT        (2 << 8)
> +#define CORE_HC_MCLK_SEL_HS400       (3 << 8)
> +#define CORE_HC_MCLK_SEL_MASK        (3 << 8)
> +#define CORE_HC_SELECT_IN_EN BIT(18)
> +#define CORE_HC_SELECT_IN_HS400      (6 << 19)
> +#define CORE_HC_SELECT_IN_MASK       (7 << 19)
> +
> +/* HS400 DDR/SDC4 DLL calibration, needed alongside the SDR CM_DLL above */
> +#define CORE_DDR_CAL_EN              BIT(0)
> +#define CORE_DDR_DLL_LOCK    BIT(11)
> +#define CORE_PWRSAVE_DLL     BIT(3)
> +#define CORE_CMDIN_RCLK_EN   BIT(1)
> +#define DDR_CONFIG_POR_VAL   0x80040873
> +
> +#define CORE_FREQ_100MHZ     (100 * 1000000)
> +#define MAX_PHASES           16
> +
>  #define MHZ(X) ((X) * 1000000UL)
>  
>  struct msm_sdhc_plat {
> @@ -50,14 +98,39 @@ struct msm_sdhc {
>       void *base;
>       struct clk_bulk clks;
>       struct udevice *vqmmc;
> +
> +     /* HS200/HS400 tuning and calibration state */
> +     bool tuning_done;
> +     bool calibration_done;
> +     u8 saved_tuning_phase;
> +
> +     /* DLL init sequence variant selection, from the SDCC core version */
> +     u32 dll_config;         /* qcom,dll-config DT override, 0 if absent */
> +     bool use_14lpp_dll_reset;       /* core_minor >= 0x42 */
> +     bool uses_tassadar_dll;         /* core_minor >= 0x71 */
> +
> +     /* HS400 support */
> +     bool use_cdclp533;              /* core_minor < 0x34, legacy 
> calibration path */
> +     bool updated_ddr_cfg;           /* core_minor >= 0x49 */
> +     u32 ddr_config;                 /* qcom,ddr-config DT override, else 
> POR value */
>  };
>  
>  struct msm_sdhc_variant_info {
>       bool mci_removed;
>  
>       u32 core_dll_config;
> +     u32 core_dll_status;
> +     u32 core_dll_config_2;
> +     u32 core_dll_config_3;
> +     u32 core_dll_usr_ctl; /* Present on SDCC5.1 onwards */
>       u32 core_vendor_spec;
>       u32 core_vendor_spec_capabilities0;
> +
> +     /* HS400 DDR/SDC4 calibration registers */
> +     u32 core_ddr_200_cfg;
> +     u32 core_vendor_spec3;
> +     u32 core_ddr_config_old; /* Applicable to sdcc minor ver < 0x49; 0 if 
> N/A */
> +     u32 core_ddr_config;
>  };
>  
>  static int msm_sdc_clk_init(struct udevice *dev)
> @@ -149,12 +222,624 @@ static int msm_sdc_mci_init(struct msm_sdhc *prv)
>       return 0;
>  }
>  
> +static int msm_dll_poll_ck_out_en(struct sdhci_host *host, u8 poll)
> +{
> +     u32 ck_out_en;
> +     struct udevice *dev = host->mmc->dev;
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     int ret;
> +
> +     ret = read_poll_timeout(readl, ck_out_en,
> +                             (!!(ck_out_en & CORE_CK_OUT_EN) == poll),
> +                             1, 50,
> +                             host->ioaddr + var_info->core_dll_config);
> +     if (ret) {
> +             printf("%s: CK_OUT_EN bit is not %d\n",
> +                    host->name, poll);
> +             return -ETIMEDOUT;
> +     }
> +
> +     return 0;
> +}
> +
> +static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase)
> +{
> +     int rc;
> +     static const u8 grey_coded_phase_table[] = {
> +             0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4,
> +             0xc, 0xd, 0xf, 0xe, 0xa, 0xb, 0x9, 0x8
> +     };
> +     u32 config;
> +     struct udevice *dev = host->mmc->dev;
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +
> +     if (phase > 0xf)
> +             return -EINVAL;
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config &= ~(CORE_CDR_EN | CORE_CK_OUT_EN);
> +     config |= (CORE_CDR_EXT_EN | CORE_DLL_EN);
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     rc = msm_dll_poll_ck_out_en(host, 0);
> +     if (rc)
> +             return rc;
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config &= ~CDR_SELEXT_MASK;
> +     config |= grey_coded_phase_table[phase] << CDR_SELEXT_SHIFT;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config |= CORE_CK_OUT_EN;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     rc = msm_dll_poll_ck_out_en(host, 1);
> +     if (rc)
> +             return rc;
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config |= CORE_CDR_EN;
> +     config &= ~CORE_CDR_EXT_EN;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     return 0;
> +}
> +
> +/*
> + * Programs the MCLK_FREQ (CMUX_SHIFT_PHASE) field. Must be set before
> + * enabling CORE_DLL_EN/CORE_CK_OUT_EN, otherwise the DLL will not lock.
> + *
> + * host->clock is not updated by the generic sdhci.c, so mmc->clock is
> + * used instead.
> + */
> +static void msm_cm_dll_set_freq(struct sdhci_host *host)
> +{
> +     struct udevice *dev = host->mmc->dev;
> +     struct mmc *mmc = host->mmc;
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     u32 mclk_freq = 0, config;
> +     unsigned int clock = mmc->clock;
> +
> +     if (clock <= 112000000)
> +             mclk_freq = 0;
> +     else if (clock <= 125000000)
> +             mclk_freq = 1;
> +     else if (clock <= 137000000)
> +             mclk_freq = 2;
> +     else if (clock <= 150000000)
> +             mclk_freq = 3;
> +     else if (clock <= 162000000)
> +             mclk_freq = 4;
> +     else if (clock <= 175000000)
> +             mclk_freq = 5;
> +     else if (clock <= 187000000)
> +             mclk_freq = 6;
> +     else if (clock <= 200000000)
> +             mclk_freq = 7;
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config &= ~CMUX_SHIFT_PHASE_MASK;
> +     config |= mclk_freq << CMUX_SHIFT_PHASE_SHIFT;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +}
> +
> +static int msm_dll_poll_lock_status(struct sdhci_host *host)
> +{
> +     u32 dll_status;
> +     struct udevice *dev = host->mmc->dev;
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     int ret;
> +
> +     ret = read_poll_timeout(readl, dll_status,
> +                             (dll_status & CORE_DLL_LOCK),
> +                             1, 50,
> +                             host->ioaddr + var_info->core_dll_status);
> +     if (ret) {
> +             printf("%s: DLL failed to LOCK (DLL_STATUS=0x%08x)\n",
> +                    host->name,
> +                    readl(host->ioaddr + var_info->core_dll_status));
> +             return -ETIMEDOUT;
> +     }
> +
> +     return 0;
> +}
> +
> +/* CM_DLL initialization sequence */
> +static int msm_init_cm_dll(struct sdhci_host *host)
> +{
> +     struct udevice *dev = host->mmc->dev;
> +     struct msm_sdhc *priv = dev_get_priv(dev);
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     u32 config;
> +     int ret;
> +
> +     /*
> +      * Keep the clock enabled while DLL tuning is in progress; PWRSAVE
> +      * may otherwise turn it off.
> +      */
> +     config = readl(host->ioaddr + var_info->core_vendor_spec);
> +     config &= ~CORE_CLK_PWRSAVE;
> +     writel(config, host->ioaddr + var_info->core_vendor_spec);
> +
> +     if (priv->dll_config)
> +             writel(priv->dll_config, host->ioaddr + 
> var_info->core_dll_config);
> +
> +     if (priv->use_14lpp_dll_reset) {
> +             config = readl(host->ioaddr + var_info->core_dll_config);
> +             config &= ~CORE_CK_OUT_EN;
> +             writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +             if (var_info->core_dll_config_2) {
> +                     config = readl(host->ioaddr + 
> var_info->core_dll_config_2);
> +                     config |= CORE_DLL_CLOCK_DISABLE;
> +                     writel(config, host->ioaddr + 
> var_info->core_dll_config_2);
> +             }
> +     }
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config |= CORE_DLL_RST;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config |= CORE_DLL_PDN;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     /*
> +      * MCLK_FREQ must be programmed while the DLL is reset and powered
> +      * down, unless a DT dll-config override is in effect.
> +      */
> +     if (!priv->dll_config)
> +             msm_cm_dll_set_freq(host);
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config &= ~CORE_DLL_RST;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config &= ~CORE_DLL_PDN;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     if (priv->use_14lpp_dll_reset) {
> +             if (!priv->dll_config)
> +                     msm_cm_dll_set_freq(host);
> +
> +             if (var_info->core_dll_config_2) {
> +                     config = readl(host->ioaddr + 
> var_info->core_dll_config_2);
> +                     config &= ~CORE_DLL_CLOCK_DISABLE;
> +                     writel(config, host->ioaddr + 
> var_info->core_dll_config_2);
> +             }
> +     }
> +
> +     /* Applicable to SDCC v5.1 onwards only */
> +     if (priv->uses_tassadar_dll && var_info->core_dll_usr_ctl) {
> +             config = DLL_USR_CTL_POR_VAL | FINE_TUNE_MODE_EN |
> +                      ENABLE_DLL_LOCK_STATUS | BIAS_OK_SIGNAL;
> +             writel(config, host->ioaddr + var_info->core_dll_usr_ctl);
> +
> +             if (var_info->core_dll_config_3) {
> +                     config = readl(host->ioaddr + 
> var_info->core_dll_config_3);
> +                     config &= ~0xFF;
> +                     if (host->mmc->clock < 150000000)
> +                             config |= DLL_CONFIG_3_LOW_FREQ_VAL;
> +                     else
> +                             config |= DLL_CONFIG_3_HIGH_FREQ_VAL;
> +                     writel(config, host->ioaddr + 
> var_info->core_dll_config_3);
> +             }
> +     }
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config |= CORE_DLL_EN;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config |= CORE_CK_OUT_EN;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     ret = msm_dll_poll_lock_status(host);
> +     if (ret)
> +             return ret;
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config |= CORE_CDR_EN;
> +     config &= ~CORE_CDR_EXT_EN;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     priv->calibration_done = false;
> +
> +     return 0;
> +}
> +
> +/*
> + * HS400 DDR calibration via SDC4 CM_DLL, used when use_cdclp533 is false
> + * (core_minor >= 0x34).
> + */
> +static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host)
> +{
> +     struct udevice *dev = host->mmc->dev;
> +     struct msm_sdhc *priv = dev_get_priv(dev);
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     u32 config, ddr_cfg_offset, wait_cnt;
> +     int ret;
> +
> +     /*
> +      * core_ddr_config defaults to the desired configuration on reset;
> +      * reprogram the POR value in case an earlier bootloader stage
> +      * modified it.
> +      */
> +     if (priv->updated_ddr_cfg)
> +             ddr_cfg_offset = var_info->core_ddr_config;
> +     else
> +             ddr_cfg_offset = var_info->core_ddr_config_old;
> +     writel(priv->ddr_config, host->ioaddr + ddr_cfg_offset);
> +
> +     config = readl(host->ioaddr + var_info->core_ddr_200_cfg);
> +     config &= ~CORE_CMDIN_RCLK_EN;
> +     writel(config, host->ioaddr + var_info->core_ddr_200_cfg);
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config_2);
> +     config |= CORE_DDR_CAL_EN;
> +     writel(config, host->ioaddr + var_info->core_dll_config_2);
> +
> +     ret = -ETIMEDOUT;
> +     wait_cnt = 100;
> +     while (wait_cnt--) {
> +             if (readl(host->ioaddr + var_info->core_dll_status) &
> +                 CORE_DDR_DLL_LOCK) {
> +                     ret = 0;
> +                     break;
> +             }
> +             udelay(10);
> +     }
> +
> +     if (ret) {
> +             printf("%s: CM_DLL_SDC4 calibration was not completed\n",
> +                    host->name);
> +             return ret;
> +     }
> +
> +     /*
> +      * Skip CORE_PWRSAVE_DLL for the 14lpp DLL reset variant: it cannot
> +      * guarantee the MCLK-gating timing PWRSAVE_DLL depends on.
> +      */
> +     if (!priv->use_14lpp_dll_reset) {
> +             config = readl(host->ioaddr + var_info->core_vendor_spec3);
> +             config |= CORE_PWRSAVE_DLL;
> +             writel(config, host->ioaddr + var_info->core_vendor_spec3);
> +     }
> +
> +     return 0;
> +}
> +
> +/*
> + * HS400 DLL calibration, performed once when transitioning into HS400 at
> + * clock > 100MHz.
> + */
> +static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host)
> +{
> +     struct udevice *dev = host->mmc->dev;
> +     struct msm_sdhc *priv = dev_get_priv(dev);
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     u32 config;
> +     int ret;
> +
> +     ret = msm_init_cm_dll(host);
> +     if (ret)
> +             return ret;
> +
> +     /* Restore the phase found during HS200 tuning */
> +     ret = msm_config_cm_dll_phase(host, priv->saved_tuning_phase);
> +     if (ret)
> +             return ret;
> +
> +     config = readl(host->ioaddr + var_info->core_dll_config);
> +     config |= CORE_CMD_DAT_TRACK_SEL;
> +     writel(config, host->ioaddr + var_info->core_dll_config);
> +
> +     /*
> +      * use_cdclp533 only applies to legacy SDCC (core_minor < 0x34); our
> +      * supported targets always use the SDC4 calibration path.
> +      */
> +     if (priv->use_cdclp533) {
> +             printf("%s: CDCLP533 HS400 calibration path is not 
> implemented\n",
> +                    host->name);
> +             return -EOPNOTSUPP;
> +     }
> +
> +     return sdhci_msm_cm_dll_sdc4_calibration(host);
> +}
> +
> +static int msm_find_most_appropriate_phase(struct sdhci_host *host,
> +                                        u8 *phase_table,
> +                                        u8 total_phases)
> +{
> +     int ret;
> +     u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} };
> +     u8 phases_per_row[MAX_PHASES] = { 0 };
> +     int row_index = 0, col_index = 0, selected_row_index = 0;
> +     int i, longest_range_len = 0;
> +     bool found = false;
> +
> +     if (!total_phases || total_phases > MAX_PHASES) {
> +             printf("%s: Invalid argument: total_phases=%d\n",
> +                    host->name, total_phases);
> +             return -EINVAL;
> +     }
> +
> +     for (i = 0; i < total_phases; i++) {
> +             ranges[row_index][col_index] = phase_table[i];
> +             phases_per_row[row_index] += 1;
> +             col_index++;
> +
> +             if ((i + 1) == total_phases)
> +                     continue;
> +
> +             if (phase_table[i] + 1 != phase_table[i + 1]) {
> +                     row_index++;
> +                     col_index = 0;
> +             }
> +     }
> +
> +     if (row_index == 0) {
> +             ret = phase_table[total_phases / 2];
> +             goto exit;
> +     }
> +
> +     for (i = 0; i <= row_index; i++) {
> +             if (phases_per_row[i] > longest_range_len) {
> +                     longest_range_len = phases_per_row[i];
> +                     selected_row_index = i;
> +                     found = true;
> +             }
> +     }
> +
> +     if (found) {
> +             ret = ranges[selected_row_index][longest_range_len / 2];
> +     } else {
> +             ret = -EIO;
> +             printf("%s: Failed to find a valid phase\n", host->name);
> +     }
> +
> +exit:
> +     return ret;
> +}
> +
> +static bool sdhci_msm_is_tuning_needed(struct sdhci_host *host)
> +{
> +     struct mmc *mmc = host->mmc;
> +
> +     return mmc->selected_mode == MMC_HS_200;
> +}
> +
> +static int sdhci_msm_execute_tuning(struct mmc *mmc, u8 opcode)
> +{
> +     struct sdhci_host *host = mmc->priv;
> +     int tuning_seq_cnt = 10;
> +     u8 phase, tuned_phases[MAX_PHASES], tuned_phase_cnt = 0;
> +     int rc;
> +     struct udevice *dev = mmc->dev;
> +     struct msm_sdhc *priv = dev_get_priv(dev);
> +
> +     if (!sdhci_msm_is_tuning_needed(host))
> +             return 0;
> +
> +     /*
> +      * The SDHCI core may call execute_tuning before host->clock is
> +      * updated to match mmc->clock.
> +      */
> +     if (host->clock != mmc->clock) {
> +             rc = sdhci_set_clock(mmc, mmc->clock);
> +             if (rc) {
> +                     printf("%s: Failed to set clock for tuning\n", 
> host->name);
> +                     return rc;
> +             }
> +     }
> +
> +     priv->tuning_done = false;
> +
> +retry:
> +     rc = msm_init_cm_dll(host);
> +     if (rc) {
> +             printf("%s: Failed to init DLL\n", host->name);
> +             return rc;
> +     }
> +
> +     phase = 0;
> +     tuned_phase_cnt = 0;
> +
> +     do {
> +             rc = msm_config_cm_dll_phase(host, phase);
> +             if (rc) {
> +                     printf("%s: Failed to set DLL phase %d\n",
> +                            host->name, phase);
> +                     return rc;
> +             }
> +
> +             rc = mmc_send_tuning(mmc, opcode);
> +             if (!rc)
> +                     tuned_phases[tuned_phase_cnt++] = phase;
> +     } while (++phase < MAX_PHASES);
> +
> +     if (tuned_phase_cnt) {
> +             if (tuned_phase_cnt == MAX_PHASES) {
> +                     /*
> +                      * All phases valid is close to as bad as none valid:
> +                      * likely no phase is really reliable. Retry a few
> +                      * times rather than guessing.
> +                      */
> +                     if (--tuning_seq_cnt) {
> +                             tuned_phase_cnt = 0;
> +                             goto retry;
> +                     }
> +             }
> +
> +             rc = msm_find_most_appropriate_phase(host, tuned_phases,
> +                                                  tuned_phase_cnt);
> +             if (rc < 0) {
> +                     printf("%s: Failed to find appropriate phase\n",
> +                            host->name);
> +                     return rc;
> +             }
> +             phase = rc;
> +
> +             rc = msm_config_cm_dll_phase(host, phase);
> +             if (rc) {
> +                     printf("%s: Failed to set final phase %d\n",
> +                            host->name, phase);
> +                     return rc;
> +             }
> +
> +             priv->saved_tuning_phase = phase;
> +     } else {
> +             if (--tuning_seq_cnt)
> +                     goto retry;
> +             printf("%s: No tuning point found\n", host->name);
> +             rc = -EIO;
> +     }
> +
> +     if (!rc)
> +             priv->tuning_done = true;
> +
> +     return rc;
> +}
> +
> +/*
> + * Configure HC mode selection. On the first HS400 transition,
> + * calibration_done is still false, so the HS400 mux-select bits are
> + * deferred until calibration completes in msm_sdhci_config_dll(); see
> + * sdhci_msm_hs400_select_in().
> + */
> +static void sdhci_msm_hc_select_mode(struct sdhci_host *host)
> +{
> +     struct mmc *mmc = host->mmc;
> +     struct udevice *dev = mmc->dev;
> +     struct msm_sdhc *priv = dev_get_priv(dev);
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     bool is_hs400 = mmc->selected_mode == MMC_HS_400;
> +     u32 config;
> +
> +     config = readl(host->ioaddr + var_info->core_vendor_spec);
> +     config &= ~CORE_HC_MCLK_SEL_MASK;
> +
> +     if (mmc->selected_mode == MMC_HS_200)
> +             config |= CORE_HC_MCLK_SEL_DFLT;
> +     else if (is_hs400)
> +             config |= CORE_HC_MCLK_SEL_HS400;
> +     else
> +             config |= CORE_HC_MCLK_SEL_DFLT;
> +
> +     writel(config, host->ioaddr + var_info->core_vendor_spec);
> +
> +     if (is_hs400) {
> +             if (priv->calibration_done) {
> +                     config = readl(host->ioaddr + 
> var_info->core_vendor_spec);
> +                     config |= CORE_HC_SELECT_IN_HS400;
> +                     config |= CORE_HC_SELECT_IN_EN;
> +                     writel(config, host->ioaddr + 
> var_info->core_vendor_spec);
> +             }
> +     } else {
> +             /*
> +              * Clear these bits for every non-HS400 mode so a previous
> +              * HS400 attempt never leaves them stuck set.
> +              */
> +             if (!priv->use_cdclp533) {
> +                     config = readl(host->ioaddr + 
> var_info->core_vendor_spec3);
> +                     config &= ~CORE_PWRSAVE_DLL;
> +                     writel(config, host->ioaddr + 
> var_info->core_vendor_spec3);
> +             }
> +
> +             config = readl(host->ioaddr + var_info->core_vendor_spec);
> +             config &= ~CORE_HC_SELECT_IN_EN;
> +             config &= ~CORE_HC_SELECT_IN_MASK;
> +             writel(config, host->ioaddr + var_info->core_vendor_spec);
> +     }
> +}
> +
> +/*
> + * Selects HS400 in the HC_SELECT_IN mux. Must only be called after
> + * sdhci_msm_hs400_dll_calibration() succeeds.
> + */
> +static void sdhci_msm_hs400_select_in(struct sdhci_host *host)
> +{
> +     struct udevice *dev = host->mmc->dev;
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     u32 config;
> +
> +     config = readl(host->ioaddr + var_info->core_vendor_spec);
> +     config |= CORE_HC_SELECT_IN_HS400;
> +     config |= CORE_HC_SELECT_IN_EN;
> +     writel(config, host->ioaddr + var_info->core_vendor_spec);
> +}
> +
> +static void sdhci_msm_set_control_reg(struct sdhci_host *host)
> +{
> +     struct mmc *mmc = host->mmc;
> +     struct udevice *dev = mmc->dev;
> +     struct msm_sdhc *priv = dev_get_priv(dev);
> +     const struct msm_sdhc_variant_info *var_info =
> +             (void *)dev_get_driver_data(dev);
> +     u32 config;
> +
> +     /*
> +      * This controller has no HOST_CONTROL2 encoding for HS400. Keep the
> +      * UHS field at SDR104 (same as HS200) for HS400 too; the data path
> +      * is switched to HS400 timing via the vendor-specific
> +      * CORE_HC_MCLK_SEL/CORE_HC_SELECT_IN bits instead.
> +      */
> +     sdhci_set_voltage(host);
> +     if (mmc->selected_mode == MMC_HS_400) {
> +             u32 ctrl2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +
> +             ctrl2 &= ~SDHCI_CTRL_UHS_MASK;
> +             ctrl2 |= SDHCI_CTRL_UHS_SDR104;
> +             sdhci_writew(host, ctrl2, SDHCI_HOST_CONTROL2);
> +     } else {
> +             sdhci_set_uhs_timing(host);
> +     }
> +
> +     sdhci_msm_hc_select_mode(host);
> +
> +     /*
> +      * Below 100MHz the feedback clock must be provided without the DLL,
> +      * so tuning can be skipped.
> +      */
> +     if (mmc->clock && mmc->clock <= CORE_FREQ_100MHZ) {
> +             if (mmc->selected_mode == MMC_HS_200 ||
> +                 mmc->selected_mode == MMC_HS_400) {
> +                     config = readl(host->ioaddr + 
> var_info->core_dll_config);
> +                     config |= CORE_DLL_RST;
> +                     writel(config, host->ioaddr + 
> var_info->core_dll_config);
> +
> +                     config = readl(host->ioaddr + 
> var_info->core_dll_config);
> +                     config |= CORE_DLL_PDN;
> +                     writel(config, host->ioaddr + 
> var_info->core_dll_config);
> +
> +                     /* Calibration must be redone once the clock returns to 
> HS400 speed. */
> +                     priv->calibration_done = false;
> +             }
> +     }
> +}
> +
>  static int msm_sdhci_config_dll(struct sdhci_host *host, u32 clock, bool 
> enable)
>  {
>       struct udevice *dev = mmc_to_dev(host->mmc);
> +     struct mmc *mmc = host->mmc;
> +     struct msm_sdhc *priv = dev_get_priv(dev);
>       const struct msm_sdhc_variant_info *var_info = (void 
> *)dev_get_driver_data(dev);
>       u32 config;
>  
> +     if (clock == 0)
> +             return 0;
> +
>       if (enable && clock < MHZ(100)) {
>               /*
>                * DLL is not required for clock <= 100MHz
> @@ -169,12 +854,33 @@ static int msm_sdhci_config_dll(struct sdhci_host 
> *host, u32 clock, bool enable)
>               writel(config, host->ioaddr + var_info->core_dll_config);
>       }
>  
> +     /*
> +      * HS400 requires a dedicated DDR/SDC4 DLL calibration step, performed
> +      * once per calibration cycle once the clock is running above 100MHz.
> +      */
> +     if (enable && clock > MHZ(100) &&
> +         mmc->selected_mode == MMC_HS_400 && priv->tuning_done &&
> +         !priv->calibration_done) {
> +             int ret = sdhci_msm_hs400_dll_calibration(host);
> +
> +             if (!ret) {
> +                     priv->calibration_done = true;
> +                     /* Safe to switch the data-path mux into HS400 timing 
> now. */
> +                     sdhci_msm_hs400_select_in(host);
> +             } else {
> +                     printf("%s: Failed to calibrate DLL for HS400 mode 
> (%d)\n",
> +                            host->name, ret);
> +                     return ret;
> +             }
> +     }
> +
>       return 0;
>  }
>  
>  struct sdhci_ops msm_sdhci_ops = {
>       .config_dll = &msm_sdhci_config_dll,
> -     .set_control_reg = &sdhci_set_control_reg,
> +     .set_control_reg = &sdhci_msm_set_control_reg,
> +     .platform_execute_tuning = &sdhci_msm_execute_tuning,
>  };
>  
>  static int msm_sdc_probe(struct udevice *dev)
> @@ -186,6 +892,7 @@ static int msm_sdc_probe(struct udevice *dev)
>       struct sdhci_host *host = &prv->host;
>       u32 core_version, core_minor, core_major;
>       struct reset_ctl bcr_rst;
> +     struct blk_desc *bdesc;
>       u32 caps;
>       int ret;
>  
> @@ -235,6 +942,24 @@ static int msm_sdc_probe(struct udevice *dev)
>  
>       log_debug("SDCC version %d.%d\n", core_major, core_minor);
>  
> +     /* Feature support depends on the SDCC core version. */
> +     if (core_major == 1 && core_minor >= 0x42)
> +             prv->use_14lpp_dll_reset = true;
> +
> +     if (core_major == 1 && core_minor >= 0x71)
> +             prv->uses_tassadar_dll = true;
> +
> +     if (core_major == 1 && core_minor < 0x34)
> +             prv->use_cdclp533 = true;
> +
> +     if (core_major == 1 && core_minor >= 0x49)
> +             prv->updated_ddr_cfg = true;
> +
> +     dev_read_u32(dev, "qcom,dll-config", &prv->dll_config);
> +
> +     if (dev_read_u32(dev, "qcom,ddr-config", &prv->ddr_config))
> +             prv->ddr_config = DDR_CONFIG_POR_VAL;
> +
>       /*
>        * Support for some capabilities is not advertised by newer
>        * controller versions and must be explicitly enabled.
> @@ -249,6 +974,12 @@ static int msm_sdc_probe(struct udevice *dev)
>       if (ret)
>               return ret;
>  
> +     if (plat->cfg.host_caps & MMC_CAP_NONREMOVABLE) {
> +             bdesc = mmc_get_blk_desc(&plat->mmc);
> +             if (bdesc)
> +                     bdesc->removable = 0;
> +     }
> +
>       host->mmc = &plat->mmc;
>       host->mmc->dev = dev;
>       host->ops = &msm_sdhci_ops;
> @@ -318,16 +1049,35 @@ static const struct msm_sdhc_variant_info 
> msm_sdhc_mci_var = {
>       .mci_removed = false,
>  
>       .core_dll_config = 0x100,
> +     .core_dll_status = 0x108,
> +     .core_dll_config_2 = 0x1b4,
>       .core_vendor_spec = 0x10c,
>       .core_vendor_spec_capabilities0 = 0x11c,
> +
> +     .core_ddr_200_cfg = 0x184,
> +     .core_vendor_spec3 = 0x1b0,
> +     .core_ddr_config_old = 0x1b8,
> +     .core_ddr_config = 0x1bc,
>  };
>  
>  static const struct msm_sdhc_variant_info msm_sdhc_v5_var = {
>       .mci_removed = true,
>  
>       .core_dll_config = 0x200,
> +     .core_dll_status = 0x208,
> +     .core_dll_config_2 = 0x254,
> +     .core_dll_config_3 = 0x258,
> +     .core_dll_usr_ctl = 0x388,
>       .core_vendor_spec = 0x20c,
>       .core_vendor_spec_capabilities0 = 0x21c,
> +
> +     .core_ddr_200_cfg = 0x224,
> +     .core_vendor_spec3 = 0x250,
> +     .core_ddr_config = 0x25c,
> +     /*
> +      * core_ddr_config_old not present on V5 - updated_ddr_cfg is
> +      * effectively always true here since V5 implies core_minor >= 0x49
> +      */
>  };
>  
>  static const struct udevice_id msm_mmc_ids[] = {

-- 
// Casey (she/her)

Reply via email to