Re: [PATCH v3 8/8] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support

Adrian Hunter <[email protected]>
Newsgroups org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-mmc
Organization Intel Finland Oy, Registered Address: c/o Alberga Business Park, 6 krs, Bertel Jungin Aukio 5, 02600 Espoo, Business Identity Code: 0357606 - 4, Domiciled in Helsinki
Message-ID <[email protected]>
On 24/07/2026 17:50, Tanmay Kathpalia wrote:
> The Altera Agilex5 SoC integrates a Cadence SD6HC controller that needs
> platform-specific configuration to operate correctly.
> 
> The SoC requires three named resets: "sdhc-reset", "combophy", and
> "sdmmc-ocp". All three are exclusive and must be asserted together before
> being released, so the SDHCI, SoftPHY, and OCP/AXI clock domains cross the
> reset boundary simultaneously. SoftPHY is shared with NAND at the SoC
> level, but only one of SDMMC or NAND is enabled on a given board.
> 
> The IOMMU maps DMA addresses within a 40-bit physical address space, so
> the DMA mask is capped at 40 bits to prevent allocation beyond the
> controller's reach.
> 
> The silicon requires the MULTIBLOCK_READ_ACMD12, CAP_CLOCK_BASE_BROKEN,
> PRESET_VALUE_BROKEN, and ACMD23_BROKEN quirks. Since
> CAP_CLOCK_BASE_BROKEN prevents reading the base clock from the
> capabilities register, the maximum clock is supplied from the platform
> clock instead.
> 
> Signed-off-by: Tanmay Kathpalia <[email protected]>

Acked-by: Adrian Hunter <[email protected]>

> ---
>  drivers/mmc/host/sdhci-cadence-core.c | 113 ++++++++++++++++++++++++++
>  1 file changed, 113 insertions(+)
> 
> diff --git a/drivers/mmc/host/sdhci-cadence-core.c b/drivers/mmc/host/sdhci-cadence-core.c
> index 18846acc0b11..24130655a385 100644
> --- a/drivers/mmc/host/sdhci-cadence-core.c
> +++ b/drivers/mmc/host/sdhci-cadence-core.c
> @@ -7,6 +7,7 @@
>  
>  #include <linux/bitfield.h>
>  #include <linux/bits.h>
> +#include <linux/dma-mapping.h>
>  #include <linux/iopoll.h>
>  #include <linux/module.h>
>  #include <linux/mmc/host.h>
> @@ -90,6 +91,7 @@ struct sdhci_cdns4_phy_cfg {
>  struct sdhci_cdns_drv_data {
>  	int (*init)(struct platform_device *pdev);
>  	const struct sdhci_pltfm_data pltfm_data;
> +	u64 dma_mask;
>  };
>  
>  static const struct sdhci_cdns4_phy_cfg sdhci_cdns4_phy_cfgs[] = {
> @@ -196,6 +198,23 @@ static unsigned int sdhci_cdns_get_timeout_clock(struct sdhci_host *host)
>  	return host->max_clk;
>  }
>  
> +static int sdhci_cdns_set_dma_mask(struct sdhci_host *host)
> +{
> +	const struct sdhci_cdns_drv_data *data;
> +	struct device *dev = mmc_dev(host->mmc);
> +	int ret;
> +
> +	data = of_device_get_match_data(dev);
> +	if (!data || !data->dma_mask)
> +		return 0;
> +
> +	ret = dma_set_mask_and_coherent(dev, data->dma_mask);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to set DMA mask\n");
> +
> +	return 0;
> +}
> +
>  static void sdhci_cdns_set_emmc_mode(struct sdhci_cdns_priv *priv, u32 mode)
>  {
>  	u32 tmp;
> @@ -462,6 +481,72 @@ static int elba_drv_init(struct platform_device *pdev)
>  	return 0;
>  }
>  
> +static int sdhci_cdns6_agilex5_init(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct reset_control *rst_sdhc;
> +	struct reset_control *rst_combophy;
> +	struct reset_control *rst_ocp;
> +	int ret;
> +
> +	/*
> +	 * Assert SDHCI, SoftPHY (combophy), and SDMMC OCP/AXI resets together
> +	 * so their active periods overlap before all domains are released.
> +	 * SoftPHY is shared with NAND, but only one of SDMMC
> +	 * or NAND is enabled on a given board.
> +	 */
> +	rst_sdhc = devm_reset_control_get_exclusive(dev, "sdhc-reset");
> +	if (IS_ERR(rst_sdhc))
> +		return dev_err_probe(dev, PTR_ERR(rst_sdhc), "failed to get sdhc-reset\n");
> +
> +	rst_combophy = devm_reset_control_get_exclusive(dev, "combophy");
> +	if (IS_ERR(rst_combophy))
> +		return dev_err_probe(dev, PTR_ERR(rst_combophy), "failed to get combophy reset\n");
> +
> +	rst_ocp = devm_reset_control_get_exclusive(dev, "sdmmc-ocp");
> +	if (IS_ERR(rst_ocp))
> +		return dev_err_probe(dev, PTR_ERR(rst_ocp), "failed to get sdmmc-ocp reset\n");
> +
> +	ret = reset_control_assert(rst_sdhc);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to assert sdhc-reset\n");
> +
> +	ret = reset_control_assert(rst_combophy);
> +	if (ret) {
> +		reset_control_deassert(rst_sdhc);
> +		return dev_err_probe(dev, ret, "failed to assert combophy reset\n");
> +	}
> +
> +	ret = reset_control_assert(rst_ocp);
> +	if (ret) {
> +		reset_control_deassert(rst_combophy);
> +		reset_control_deassert(rst_sdhc);
> +		return dev_err_probe(dev, ret, "failed to assert sdmmc-ocp reset\n");
> +	}
> +
> +	/* Hold resets asserted long enough for all clock domains to capture. */
> +	usleep_range(10, 20);
> +
> +	ret = reset_control_deassert(rst_sdhc);
> +	if (ret) {
> +		reset_control_deassert(rst_combophy);
> +		reset_control_deassert(rst_ocp);
> +		return dev_err_probe(dev, ret, "failed to deassert sdhc-reset\n");
> +	}
> +
> +	ret = reset_control_deassert(rst_combophy);
> +	if (ret) {
> +		reset_control_deassert(rst_ocp);
> +		return dev_err_probe(dev, ret, "failed to deassert combophy reset\n");
> +	}
> +
> +	ret = reset_control_deassert(rst_ocp);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to deassert sdmmc-ocp reset\n");
> +
> +	return 0;
> +}
> +
>  static const struct sdhci_ops sdhci_cdns4_ops = {
>  	.set_clock = sdhci_set_clock,
>  	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
> @@ -481,6 +566,18 @@ static const struct sdhci_ops sdhci_cdns6_ops = {
>  	.hw_reset = sdhci_cdns6_hw_reset,
>  };
>  
> +static const struct sdhci_ops sdhci_cdns6_agilex5_ops = {
> +	.set_clock = sdhci_set_clock,
> +	.get_max_clock = sdhci_pltfm_clk_get_max_clock,
> +	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
> +	.set_bus_width = sdhci_set_bus_width,
> +	.reset = sdhci_reset,
> +	.platform_execute_tuning = sdhci_cdns_execute_tuning,
> +	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
> +	.hw_reset = sdhci_cdns6_hw_reset,
> +	.set_dma_mask = sdhci_cdns_set_dma_mask,
> +};
> +
>  static const struct sdhci_cdns_drv_data sdhci_cdns_uniphier_drv_data = {
>  	.pltfm_data = {
>  		.ops = &sdhci_cdns4_ops,
> @@ -508,6 +605,18 @@ static const struct sdhci_cdns_drv_data sdhci_cdns4_drv_data = {
>  	},
>  };
>  
> +static const struct sdhci_cdns_drv_data sdhci_cdns6_agilex5_drv_data = {
> +	.init = sdhci_cdns6_agilex5_init,
> +	.pltfm_data = {
> +		.ops = &sdhci_cdns6_agilex5_ops,
> +		.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
> +			  SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
> +		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
> +			   SDHCI_QUIRK2_ACMD23_BROKEN,
> +	},
> +	.dma_mask = DMA_BIT_MASK(40),
> +};
> +
>  static const struct sdhci_cdns_drv_data sdhci_cdns6_drv_data = {
>  	.pltfm_data = {
>  		.ops = &sdhci_cdns6_ops,
> @@ -709,6 +818,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
>  		.compatible = "cdns,sd4hc",
>  		.data = &sdhci_cdns4_drv_data,
>  	},
> +	{
> +		.compatible = "altr,agilex5-sd6hc",
> +		.data = &sdhci_cdns6_agilex5_drv_data,
> +	},
>  	{
>  		.compatible = "cdns,sd6hc",
>  		.data = &sdhci_cdns6_drv_data,
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.