From: Nick Hawkins Date: Mon, 16 Mar 2026 15:01:15 +0000 (-0500) Subject: mmc: sdhci-of-dwcmshc: Add HPE GSC eMMC support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e6375787bfe8f69fa17b5778f201ba7291ebcf15;p=thirdparty%2Fkernel%2Fstable.git mmc: sdhci-of-dwcmshc: Add HPE GSC eMMC support Add support for the eMMC controller integrated in the HPE GSC (ARM64 Cortex-A53) BMC SoC under the new 'hpe,gsc-dwcmshc' compatible string. The HPE GSC eMMC controller is based on the DesignWare Cores MSHC IP but requires several platform-specific adjustments: Clock mux (dwcmshc_hpe_set_clock): The GSC SoC wires SDHCI_CLOCK_CONTROL.freq_sel directly to a clock mux rather than a divider. Forcing freq_sel = 1 when the requested clock is 200 MHz (HS200) selects the correct high-speed clock source. Using the generic sdhci_set_clock() would otherwise leave the mux on the wrong source after tuning. Auto-tuning / vendor config (dwcmshc_hpe_vendor_specific): Disables the command-conflict check (DWCMSHC_HOST_CTRL3 BIT(0)) and programs the ATCTRL register using existing AT_CTRL_* macros: AT_CTRL_AT_EN auto-tuning circuit enable AT_CTRL_SWIN_TH_EN sampling window threshold enable AT_CTRL_TUNE_CLK_STOP_EN tune-clock-stop enable PRE_CHANGE_DLY = 3 pre-change delay POST_CHANGE_DLY = 3 post-change delay SWIN_TH_VAL = 2 sampling window threshold This combination is required for reliable HS200 signal integrity on the GSC PCB trace topology. eMMC mode (dwcmshc_hpe_set_emmc): Helper that sets DWCMSHC_CARD_IS_EMMC unconditionally. Called from both the reset and UHS-signaling paths. Reset (dwcmshc_hpe_reset): Calls dwcmshc_reset(), re-applies the vendor config above via dwcmshc_hpe_vendor_specific(), and then calls dwcmshc_hpe_set_emmc(). The GSC controller clears the CARD_IS_EMMC bit on every reset; leaving it clear causes card-detect mis-identification on an eMMC-only slot. UHS signaling (dwcmshc_hpe_set_uhs_signaling): Wraps dwcmshc_set_uhs_signaling() and calls dwcmshc_hpe_set_emmc() to ensure CARD_IS_EMMC is set for all timing modes, not just HS400. Init (dwcmshc_hpe_gsc_init): Obtains the SoC register block and MSHCCS offset via the 'hpe,gxp-sysreg' syscon phandle argument and sets SCGSyncDis (BIT(18)) to allow the HS200 RX delay lines to settle while the card clock is stopped during auto-tuning. Enables SDHCI v4 mode. Quirks: SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN: base clock not advertised in capabilities; must be obtained from the DTS 'clocks' property. SDHCI_QUIRK2_PRESET_VALUE_BROKEN: preset-value registers are not populated in the GSC ROM. All HPE-specific code is isolated to the new hpe_gsc_init / hpe_ops / hpe_gsc_pdata symbols. No existing platform (Rockchip, T-Head, sg2042, etc.) is affected. Signed-off-by: Nick Hawkins Acked-by: Adrian Hunter Signed-off-by: Ulf Hansson --- diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index 2b75a36c096b..9dc7498d422d 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -40,7 +40,10 @@ #define DWCMSHC_AREA1_MASK GENMASK(11, 0) /* Offset inside the vendor area 1 */ #define DWCMSHC_HOST_CTRL3 0x8 +#define DWCMSHC_HOST_CTRL3_CMD_CONFLICT BIT(0) #define DWCMSHC_EMMC_CONTROL 0x2c +/* HPE GSC SoC MSHCCS register */ +#define HPE_GSC_MSHCCS_SCGSYNCDIS BIT(18) #define DWCMSHC_CARD_IS_EMMC BIT(0) #define DWCMSHC_ENHANCED_STROBE BIT(8) #define DWCMSHC_EMMC_ATCTRL 0x40 @@ -1245,6 +1248,126 @@ static int sg2042_init(struct device *dev, struct sdhci_host *host, ARRAY_SIZE(clk_ids), clk_ids); } +/* + * HPE GSC-specific vendor configuration: disable command conflict check + * and program Auto-Tuning Control register. + */ +static void dwcmshc_hpe_vendor_specific(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + u32 atctrl; + u8 extra; + + extra = sdhci_readb(host, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3); + extra &= ~DWCMSHC_HOST_CTRL3_CMD_CONFLICT; + sdhci_writeb(host, extra, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3); + + atctrl = AT_CTRL_AT_EN | AT_CTRL_SWIN_TH_EN | AT_CTRL_TUNE_CLK_STOP_EN | + FIELD_PREP(AT_CTRL_PRE_CHANGE_DLY_MASK, 3) | + FIELD_PREP(AT_CTRL_POST_CHANGE_DLY_MASK, AT_CTRL_POST_CHANGE_DLY) | + FIELD_PREP(AT_CTRL_SWIN_TH_VAL_MASK, 2); + sdhci_writel(host, atctrl, dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL); +} + +static void dwcmshc_hpe_set_emmc(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + u16 ctrl; + + ctrl = sdhci_readw(host, dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL); + ctrl |= DWCMSHC_CARD_IS_EMMC; + sdhci_writew(host, ctrl, dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL); +} + +static void dwcmshc_hpe_reset(struct sdhci_host *host, u8 mask) +{ + dwcmshc_reset(host, mask); + dwcmshc_hpe_vendor_specific(host); + dwcmshc_hpe_set_emmc(host); +} + +static void dwcmshc_hpe_set_uhs_signaling(struct sdhci_host *host, unsigned int timing) +{ + dwcmshc_set_uhs_signaling(host, timing); + dwcmshc_hpe_set_emmc(host); +} + +/* + * HPE GSC eMMC controller clock setup. + * + * The GSC SoC wires the freq_sel field of SDHCI_CLOCK_CONTROL directly to a + * clock mux rather than a divider. Force freq_sel = 1 when running at + * 200 MHz (HS200) so the mux selects the correct clock source. + */ +static void dwcmshc_hpe_set_clock(struct sdhci_host *host, unsigned int clock) +{ + u16 clk; + + host->mmc->actual_clock = 0; + + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + + if (clock == 0) + return; + + clk = sdhci_calc_clk(host, clock, &host->mmc->actual_clock); + + if (host->mmc->actual_clock == 200000000) + clk |= (1 << SDHCI_DIVIDER_SHIFT); + + sdhci_enable_clk(host, clk); +} + +/* + * HPE GSC eMMC controller init. + * + * The GSC SoC requires configuring MSHCCS. Bit 18 (SCGSyncDis) disables clock + * synchronisation for phase-select values going to the HS200 RX delay lines, + * allowing the card clock to be stopped while the delay selection settles and + * the phase shift is applied. This must be used together with the ATCTRL + * settings programmed in dwcmshc_hpe_vendor_specific(): + * AT_CTRL_R.TUNE_CLK_STOP_EN = 0x1 + * AT_CTRL_R.POST_CHANGE_DLY = 0x3 + * AT_CTRL_R.PRE_CHANGE_DLY = 0x3 + * + * The DTS node provides a syscon phandle ('hpe,gxp-sysreg') with the + * MSHCCS register offset as an argument. + */ +static int dwcmshc_hpe_gsc_init(struct device *dev, struct sdhci_host *host, + struct dwcmshc_priv *dwc_priv) +{ + unsigned int reg_offset; + struct regmap *soc_ctrl; + int ret; + + /* Disable cmd conflict check and configure auto-tuning */ + dwcmshc_hpe_vendor_specific(host); + + /* Look up the GXP sysreg syscon and MSHCCS offset */ + soc_ctrl = syscon_regmap_lookup_by_phandle_args(dev->of_node, + "hpe,gxp-sysreg", + 1, ®_offset); + if (IS_ERR(soc_ctrl)) { + dev_err(dev, "failed to get hpe,gxp-sysreg syscon\n"); + return PTR_ERR(soc_ctrl); + } + + /* Set SCGSyncDis (bit 18) to disable sync on HS200 RX delay lines */ + ret = regmap_update_bits(soc_ctrl, reg_offset, + HPE_GSC_MSHCCS_SCGSYNCDIS, + HPE_GSC_MSHCCS_SCGSYNCDIS); + if (ret) { + dev_err(dev, "failed to set SCGSyncDis in MSHCCS\n"); + return ret; + } + + sdhci_enable_v4_mode(host); + + return 0; +} + static void sdhci_eic7700_set_clock(struct sdhci_host *host, unsigned int clock) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -1834,6 +1957,25 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_eic7700_pdata = { .init = eic7700_init, }; +static const struct sdhci_ops sdhci_dwcmshc_hpe_ops = { + .set_clock = dwcmshc_hpe_set_clock, + .set_bus_width = sdhci_set_bus_width, + .set_uhs_signaling = dwcmshc_hpe_set_uhs_signaling, + .get_max_clock = dwcmshc_get_max_clock, + .reset = dwcmshc_hpe_reset, + .adma_write_desc = dwcmshc_adma_write_desc, + .irq = dwcmshc_cqe_irq_handler, +}; + +static const struct dwcmshc_pltfm_data sdhci_dwcmshc_hpe_gsc_pdata = { + .pdata = { + .ops = &sdhci_dwcmshc_hpe_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN, + }, + .init = dwcmshc_hpe_gsc_init, +}; + static const struct cqhci_host_ops dwcmshc_cqhci_ops = { .enable = dwcmshc_sdhci_cqe_enable, .disable = sdhci_cqe_disable, @@ -1942,6 +2084,10 @@ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { .compatible = "eswin,eic7700-dwcmshc", .data = &sdhci_dwcmshc_eic7700_pdata, }, + { + .compatible = "hpe,gsc-dwcmshc", + .data = &sdhci_dwcmshc_hpe_gsc_pdata, + }, {}, }; MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids);