[PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add HPE GSC eMMC support

nick.hawkins@hpe.com posted 2 patches 1 month ago
There is a newer version of this series
[PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add HPE GSC eMMC support
Posted by nick.hawkins@hpe.com 1 month ago
From: Nick Hawkins <nick.hawkins@hpe.com>

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 ATCTRL = 0x021f0005:
    BIT(0)       auto-tuning circuit enable
    BIT(2)       centre-phase auto-tuning
    BIT(16)      tune-clock-stop enable
    BITS[18:17]  pre-change delay = 3
    BITS[20:19]  post-change delay = 3
    BIT(25)      sample-window threshold enable
  This combination is required for reliable HS200 signal integrity on
  the GSC PCB trace topology.

Reset (dwcmshc_hpe_reset):
  Calls sdhci_reset(), re-applies the vendor config above, and then
  sets DWCMSHC_CARD_IS_EMMC unconditionally.  The GSC controller
  clears this bit on every reset; leaving it clear causes card-detect
  mis-identification on an eMMC-only slot.

UHS signaling (dwcmshc_hpe_set_uhs_signaling):
  Mirrors upstream dwcmshc_set_uhs_signaling() but always sets
  CARD_IS_EMMC regardless of timing mode, for the same reason.

Init (dwcmshc_hpe_gsc_init):
  Obtains the SoC register block via the 'hpe,gxp-sysreg' syscon
  phandle and sets SCGSyncDis (BIT(18)) in MSHCCS (offset 0x110)
  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 <nick.hawkins@hpe.com>
---
 drivers/mmc/host/sdhci-of-dwcmshc.c | 173 ++++++++++++++++++++++++++++
 1 file changed, 173 insertions(+)

diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c
index 2b75a36c096b..78f5480f4662 100644
--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
+++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
@@ -1245,6 +1245,156 @@ 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.
+ *
+ * ATCTRL value 0x021f0005 field breakdown:
+ *   BIT(0)      - Auto-tuning circuit enabled
+ *   BIT(2)      - Center-phase auto-tuning
+ *   BIT(16)     - Tune clock stop enable
+ *   BITS[18:17] - Pre-change delay = 3
+ *   BITS[20:19] - Post-change delay = 3
+ *   BIT(25)     - Sample window threshold enable
+ */
+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);
+	u8 extra;
+
+	extra = sdhci_readb(host, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3);
+	extra &= ~BIT(0);
+	sdhci_writeb(host, extra, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3);
+	sdhci_writel(host, 0x021f0005, dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);
+}
+
+static void dwcmshc_hpe_reset(struct sdhci_host *host, u8 mask)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host);
+	u16 ctrl;
+
+	dwcmshc_reset(host, mask);
+
+	dwcmshc_hpe_vendor_specific(host);
+
+	/* HPE GSC eMMC always needs CARD_IS_EMMC set after reset */
+	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_set_uhs_signaling(struct sdhci_host *host,
+					  unsigned int timing)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
+	u16 ctrl, ctrl_2;
+
+	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+
+	/* HPE GSC: always set CARD_IS_EMMC for all timing modes */
+	ctrl = sdhci_readw(host, priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL);
+	ctrl |= DWCMSHC_CARD_IS_EMMC;
+	sdhci_writew(host, ctrl, priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL);
+
+	if ((timing == MMC_TIMING_MMC_HS200) ||
+	    (timing == MMC_TIMING_UHS_SDR104))
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+	else if (timing == MMC_TIMING_UHS_SDR12)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+	else if ((timing == MMC_TIMING_UHS_SDR25) ||
+		 (timing == MMC_TIMING_MMC_HS))
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+	else if (timing == MMC_TIMING_UHS_SDR50)
+		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+	else if ((timing == MMC_TIMING_UHS_DDR50) ||
+		 (timing == MMC_TIMING_MMC_DDR52))
+		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+	else if (timing == MMC_TIMING_MMC_HS400)
+		ctrl_2 |= DWCMSHC_CTRL_HS400;
+
+	if (priv->flags & FLAG_IO_FIXED_1V8)
+		ctrl_2 |= SDHCI_CTRL_VDD_180;
+	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+}
+
+/*
+ * 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') to access
+ * this register at offset 0x110 within the SoC control block.
+ */
+#define HPE_GSC_MSHCCS_OFFSET		0x110
+#define HPE_GSC_MSHCCS_SCGSYNCDIS	BIT(18)
+
+static int dwcmshc_hpe_gsc_init(struct device *dev, struct sdhci_host *host,
+				struct dwcmshc_priv *dwc_priv)
+{
+	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 for MSHCCS access */
+	soc_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node, "hpe,gxp-sysreg");
+	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, HPE_GSC_MSHCCS_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 +1984,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 +2111,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);
-- 
2.34.1
Re: [PATCH v2 2/2] mmc: sdhci-of-dwcmshc: Add HPE GSC eMMC support
Posted by Shawn Lin 1 month ago
在 2026/03/10 星期二 5:13, nick.hawkins@hpe.com 写道:
> From: Nick Hawkins <nick.hawkins@hpe.com>
> 
> 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 ATCTRL = 0x021f0005:
>      BIT(0)       auto-tuning circuit enable
>      BIT(2)       centre-phase auto-tuning
>      BIT(16)      tune-clock-stop enable
>      BITS[18:17]  pre-change delay = 3
>      BITS[20:19]  post-change delay = 3
>      BIT(25)      sample-window threshold enable
>    This combination is required for reliable HS200 signal integrity on
>    the GSC PCB trace topology.
> 
> Reset (dwcmshc_hpe_reset):
>    Calls sdhci_reset(), re-applies the vendor config above, and then
>    sets DWCMSHC_CARD_IS_EMMC unconditionally.  The GSC controller
>    clears this bit on every reset; leaving it clear causes card-detect
>    mis-identification on an eMMC-only slot.
> 
> UHS signaling (dwcmshc_hpe_set_uhs_signaling):
>    Mirrors upstream dwcmshc_set_uhs_signaling() but always sets
>    CARD_IS_EMMC regardless of timing mode, for the same reason.
> 
> Init (dwcmshc_hpe_gsc_init):
>    Obtains the SoC register block via the 'hpe,gxp-sysreg' syscon
>    phandle and sets SCGSyncDis (BIT(18)) in MSHCCS (offset 0x110)
>    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 <nick.hawkins@hpe.com>
> ---
>   drivers/mmc/host/sdhci-of-dwcmshc.c | 173 ++++++++++++++++++++++++++++
>   1 file changed, 173 insertions(+)
> 
> diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c
> index 2b75a36c096b..78f5480f4662 100644
> --- a/drivers/mmc/host/sdhci-of-dwcmshc.c
> +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
> @@ -1245,6 +1245,156 @@ 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.
> + *
> + * ATCTRL value 0x021f0005 field breakdown:
> + *   BIT(0)      - Auto-tuning circuit enabled
> + *   BIT(2)      - Center-phase auto-tuning
> + *   BIT(16)     - Tune clock stop enable
> + *   BITS[18:17] - Pre-change delay = 3
> + *   BITS[20:19] - Post-change delay = 3
> + *   BIT(25)     - Sample window threshold enable
> + */
> +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);
> +	u8 extra;
> +
> +	extra = sdhci_readb(host, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3);
> +	extra &= ~BIT(0);
> +	sdhci_writeb(host, extra, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3);
> +	sdhci_writel(host, 0x021f0005, dwc_priv->vendor_specific_area1 + DWCMSHC_EMMC_ATCTRL);

Although you break it down in the comment, but it's still hard to read
and hard to change in the feature if needed. Would you consider defining
some macros and then OR-ing these macros together here?

> +}
> +
> +static void dwcmshc_hpe_reset(struct sdhci_host *host, u8 mask)
> +{
> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host);
> +	u16 ctrl;
> +
> +	dwcmshc_reset(host, mask);
> +
> +	dwcmshc_hpe_vendor_specific(host);
> +
> +	/* HPE GSC eMMC always needs CARD_IS_EMMC set after reset */
> +	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_set_uhs_signaling(struct sdhci_host *host,
> +					  unsigned int timing)
> +{

This entire function is 99% copied from dwcmshc_set_uhs_signaling,
please wrap it like:

static void dwcmshc_hpe_set_uhs_signaling()
{
	dwcmshc_set_uhs_signaling();
	/* HPE GSC: always set CARD_IS_EMMC for all timing modes */
	ctrl = sdhci_readw(host, priv->vendor_specific_area1 + 
DWCMSHC_EMMC_CONTROL);
	ctrl |= DWCMSHC_CARD_IS_EMMC;
	sdhci_writew(host, ctrl, priv->vendor_specific_area1 + 
DWCMSHC_EMMC_CONTROL);
	
}

> +	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
> +	struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
> +	u16 ctrl, ctrl_2;
> +
> +	ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
> +	ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
> +
> +	/* HPE GSC: always set CARD_IS_EMMC for all timing modes */
> +	ctrl = sdhci_readw(host, priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL);
> +	ctrl |= DWCMSHC_CARD_IS_EMMC;
> +	sdhci_writew(host, ctrl, priv->vendor_specific_area1 + DWCMSHC_EMMC_CONTROL);
> +
> +	if ((timing == MMC_TIMING_MMC_HS200) ||
> +	    (timing == MMC_TIMING_UHS_SDR104))
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
> +	else if (timing == MMC_TIMING_UHS_SDR12)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
> +	else if ((timing == MMC_TIMING_UHS_SDR25) ||
> +		 (timing == MMC_TIMING_MMC_HS))
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
> +	else if (timing == MMC_TIMING_UHS_SDR50)
> +		ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
> +	else if ((timing == MMC_TIMING_UHS_DDR50) ||
> +		 (timing == MMC_TIMING_MMC_DDR52))
> +		ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
> +	else if (timing == MMC_TIMING_MMC_HS400)
> +		ctrl_2 |= DWCMSHC_CTRL_HS400;
> +
> +	if (priv->flags & FLAG_IO_FIXED_1V8)
> +		ctrl_2 |= SDHCI_CTRL_VDD_180;
> +	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
> +}
> +
> +/*
> + * 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') to access
> + * this register at offset 0x110 within the SoC control block.
> + */
> +#define HPE_GSC_MSHCCS_OFFSET		0x110
> +#define HPE_GSC_MSHCCS_SCGSYNCDIS	BIT(18)
> +
> +static int dwcmshc_hpe_gsc_init(struct device *dev, struct sdhci_host *host,
> +				struct dwcmshc_priv *dwc_priv)
> +{
> +	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 for MSHCCS access */
> +	soc_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node, "hpe,gxp-sysreg");
> +	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, HPE_GSC_MSHCCS_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 +1984,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 +2111,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);
>