[PATCH v3 8/8] mmc: sdhci-cadence: add Altera Agilex5 SD6HC support
Tanmay Kathpalia <[email protected]> Fri, 24 Jul 2026 07:50:07 -0700
| Newsgroups | org.kernel.vger.linux-mmc,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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]> --- 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, -- 2.43.7