qcom: Enable HS200/HS400 eMMC modes on Qualcomm platforms
v4 cover qcom: Enable HS200/HS400 eMMC modes on Qualcomm platforms
This series adds HS200 and HS400 support for Qualcomm SDHCI controllers by implementing the required DLL initialization, tuning, and calibration sequences in the msm_sdhci driver.
Qualcomm SDHCI controllers were previously limited to High Speed and DDR52 modes due to the lack of HS200 tuning and HS400 DLL calibration support. This series enables higher-speed eMMC modes by adding the required controller-specific programming sequences while preserving support for the existing SDCC core variants.
Changes in v4:
- Split the series into logical chunks to reduce review complexity.
- Introduce msm_sdhci.h for shared definitions and improve driver organization.
Link to v3: https://lore.kernel.org/u-boot/[email protected]/
Changes in v3:
- guard execute_tuning under CONFIG_MMC_SUPPORTS_TUNING to fix undefined reference to mmc_send_tuning when tuning is disabled in SPL builds
- Dropped the "fix max-frequency read into wrong-sized variable" patch from this series, as it has already been picked up.
Link to v2: https://lore.kernel.org/u-boot/[email protected]/
Changes in v2:
- Address review feedback by simplifying tuning logic, removing dead code, and improving HS400 error handling.
Link to v1: https://lore.kernel.org/u-boot/[email protected]/
Aswin Murugan (4):
mmc: msm_sdhci: move definitions to msm_sdhci.h
mmc: msm_sdhci: add CM_DLL init and HS200 tuning support
mmc: msm_sdhci: add HS400 DDR/SDC4 DLL calibration
configs: qcom: enable CONFIG_MMC_HS400_SUPPORT
configs/qcom_defconfig | 1 + drivers/mmc/msm_sdhci.c | 722 +++++++++++++++++++++++++++++++++++++--- drivers/mmc/msm_sdhci.h | 127 +++++++ 3 files changed, 805 insertions(+), 45 deletions(-) create mode 100644 drivers/mmc/msm_sdhci.h
Signature · 1 line
2.34.1
PATCH v4 1/4 mmc: msm_sdhci: move definitions to msm_sdhci.h
Split register/bit definitions and the msm_sdhc/msm_sdhc_variant_info structs out into a new msm_sdhci.h, matching the header/source split already used by other sdhci drivers (sdhci-cadence.h, tmio-common.h, etc). No functional change; makes room for the HS200/HS400 additions that follow without growing msm_sdhci.c into one large undifferentiated file.
--- drivers/mmc/msm_sdhci.c | 45 +-------------------------------- drivers/mmc/msm_sdhci.h | 56 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 44 deletions(-) create mode 100644 drivers/mmc/msm_sdhci.h
drivers/mmc/msm_sdhci.c +1 -44
@@ -11,54 +11,11 @@#include <dm.h> #include <malloc.h> #include <reset.h> -#include <sdhci.h> #include <wait_bit.h> #include <asm/io.h> #include <linux/bitops.h> #include <power/regulator.h> - -/* Non-standard registers needed for SDHCI startup */ -#define SDCC_MCI_POWER 0x0 -#define SDCC_MCI_POWER_SW_RST BIT(7) - -/* This is undocumented register */ -#define SDCC_MCI_VERSION 0x50 -#define SDCC_V5_VERSION 0x318 - -#define SDCC_VERSION_MAJOR_SHIFT 28 -#define SDCC_VERSION_MAJOR_MASK (0xf << SDCC_VERSION_MAJOR_SHIFT) -#define SDCC_VERSION_MINOR_MASK 0xff - -#define SDCC_MCI_STATUS2 0x6C -#define SDCC_MCI_STATUS2_MCI_ACT 0x1 -#define SDCC_MCI_HC_MODE 0x78 - -#define CORE_VENDOR_SPEC_POR_VAL 0xa9c - -#define CORE_DLL_PDN BIT(29) -#define CORE_DLL_RST BIT(30) - -#define MHZ (X ) ((X) * 1000000UL) - -struct msm_sdhc_plat { -struct mmc_config cfg; -struct mmc mmc; -}; - -struct msm_sdhc { -struct sdhci_host host; -void *base; -struct clk_bulk clks; -struct udevice *vqmmc; -}; - -struct msm_sdhc_variant_info { -bool mci_removed; - -u32 core_dll_config; -u32 core_vendor_spec; -u32 core_vendor_spec_capabilities0; -}; +#include "msm_sdhci.h" static int msm_sdc_clk_init (struct udevice *dev) {
drivers/mmc/msm_sdhci.h new file +56 -0
56 lines not on this page · raw message
Signature · 1 line
2.34.1
PATCH v4 2/4 mmc: msm_sdhci: add CM_DLL init and HS200 tuning support
In reply to Aswin MuruganAdd the CM_DLL (delay-locked loop) initialization sequence and wire up platform_execute_tuning so the driver can perform HS200 tuning. The 14LPP DLL reset and Tassadar DLL variants are selected based on the SDCC core version, and an optional qcom,dll-config DT property can override the computed DLL configuration.
--- drivers/mmc/msm_sdhci.c | 411 ++++++++++++++++++++++++++++++++++++++++ drivers/mmc/msm_sdhci.h | 45 +++++ 2 files changed, 456 insertions(+)
drivers/mmc/msm_sdhci.c +411 -0
@@ -14,6 +14,7 @@#include <wait_bit.h> #include <asm/io.h> #include <linux/bitops.h> +#include <linux/iopoll.h> #include <power/regulator.h> #include "msm_sdhci.h"
@@ -106,12 +107,404 @@ 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 ; +} + +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 ; +} + +#if CONFIG_IS_ENABLED (MMC_SUPPORTS_TUNING ) +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; +} +#endif +static int msm_sdhci_config_dll (struct sdhci_host *host,u32 clock,bool enable) {struct udevice *dev =mmc_to_dev (host->mmc);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
@@ -132,6 +525,9 @@ static int msm_sdhci_config_dll(struct sdhci_host *host, u32 clock, bool enable)struct sdhci_ops msm_sdhci_ops = { .config_dll = &msm_sdhci_config_dll, .set_control_reg = &sdhci_set_control_reg, +#if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING) + .platform_execute_tuning = &sdhci_msm_execute_tuning, +#endif };static int msm_sdc_probe (struct udevice *dev)
@@ -193,6 +589,15 @@ 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; + +dev_read_u32 (dev,"qcom,dll-config" , &prv->dll_config); +/* * Support for some capabilities is not advertised by newer * controller versions and must be explicitly enabled.
@@ -283,6 +688,8 @@ 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, };
@@ -291,6 +698,10 @@ 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, };
drivers/mmc/msm_sdhci.h +45 -0
63 lines not on this page · raw message
Signature · 1 line
2.34.1
PATCH v4 3/4 mmc: msm_sdhci: add HS400 DDR/SDC4 DLL calibration
In reply to Aswin MuruganAdd the SDC4 CM_DLL based DDR calibration sequence required for HS400, along with the HC_MCLK_SEL/HC_SELECT_IN mode switching needed to move the data path into HS400 timing once calibration completes. An optional qcom,ddr-config DT property can override the DDR configuration POR value.
--- drivers/mmc/msm_sdhci.c | 266 +++++++++++++++++++++++++++++++++++++++- drivers/mmc/msm_sdhci.h | 26 ++++ 2 files changed, 291 insertions(+), 1 deletion(-)
drivers/mmc/msm_sdhci.c +265 -1
@@ -340,6 +340,107 @@ static int msm_init_cm_dll(struct sdhci_host *host)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)
@@ -496,9 +597,130 @@ retry: }#endif +/* +* 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;
@@ -519,12 +741,32 @@ 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, #if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING) .platform_execute_tuning = &sdhci_msm_execute_tuning, #endif
@@ -596,8 +838,17 @@ static int msm_sdc_probe(struct udevice *dev)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.
@@ -692,6 +943,11 @@ static const struct msm_sdhc_variant_info msm_sdhc_mci_var = { .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 = {
@@ -704,6 +960,14 @@ static const struct msm_sdhc_variant_info msm_sdhc_v5_var = { .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[] = {
drivers/mmc/msm_sdhci.h +26 -0
@@ -59,6 +59,21 @@#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
@@ -84,6 +99,11 @@ struct msm_sdhc {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 {
@@ -96,6 +116,12 @@ struct msm_sdhc_variant_info {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; };#endif /* __MSM_SDHCI_H__ */
Signature · 1 line
2.34.1
PATCH v4 4/4 configs: qcom: enable CONFIG_MMC_HS400_SUPPORT
In reply to Aswin MuruganEnable CONFIG_MMC_HS400_SUPPORT in qcom_defconfig to support HS400 tuning and DLL calibration.
--- configs/qcom_defconfig | 1 + 1 file changed, 1 insertion(+)
configs/qcom_defconfig +1 -0
@@ -101,6 +101,7 @@ CONFIG_NVMEM=y CONFIG_QCOM_GENI=y CONFIG_I2C_EEPROM=y CONFIG_SYS_MMC_MAX_BLK_COUNT=16384 +CONFIG_MMC_HS400_SUPPORT=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_ADMA=y CONFIG_MMC_SDHCI_MSM=y
Signature · 1 line
2.34.1