From: Sebastian Reichel Date: Fri, 31 Oct 2025 15:58:23 +0000 (+0100) Subject: mmc: sdhci-of-dwcmshc: Add command queue support for rockchip SOCs X-Git-Tag: v6.19-rc1~144^2~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fda1e0af7c28f96d4f33e57cf51565b0e9c14e63;p=thirdparty%2Fkernel%2Flinux.git mmc: sdhci-of-dwcmshc: Add command queue support for rockchip SOCs This adds CQE support for the Rockchip RK3588 and RK3576 platform. To be functional, the eMMC device-tree node must have a 'supports-cqe;' flag property. As the RK3576 device-tree has been upstreamed with the 'supports-cqe;' property set by default, the kernel already tried to use CQE, which results in system hang during suspend. This fixes the issue. Co-developed-by: Yifeng Zhao Signed-off-by: Yifeng Zhao Signed-off-by: Sebastian Reichel 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 c2a450aad6f09..c66a8dfad47c6 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -28,6 +28,7 @@ #include "sdhci-pltfm.h" #include "cqhci.h" +#include "sdhci-cqhci.h" #define SDHCI_DWCMSHC_ARG2_STUFF GENMASK(31, 16) @@ -87,6 +88,8 @@ #define DWCMSHC_EMMC_DLL_TXCLK 0x808 #define DWCMSHC_EMMC_DLL_STRBIN 0x80c #define DECMSHC_EMMC_DLL_CMDOUT 0x810 +#define DECMSHC_EMMC_MISC_CON 0x81C +#define MISC_INTCLK_EN BIT(1) #define DWCMSHC_EMMC_DLL_STATUS0 0x840 #define DWCMSHC_EMMC_DLL_START BIT(0) #define DWCMSHC_EMMC_DLL_LOCKED BIT(8) @@ -282,6 +285,7 @@ struct dwcmshc_priv { struct dwcmshc_pltfm_data { const struct sdhci_pltfm_data pdata; + const struct cqhci_host_ops *cqhci_host_ops; int (*init)(struct device *dev, struct sdhci_host *host, struct dwcmshc_priv *dwc_priv); void (*postinit)(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv); }; @@ -620,6 +624,68 @@ static void dwcmshc_cqhci_dumpregs(struct mmc_host *mmc) sdhci_dumpregs(mmc_priv(mmc)); } +static void rk35xx_sdhci_cqe_pre_enable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + u32 reg; + + reg = sdhci_readl(host, dwc_priv->vendor_specific_area2 + CQHCI_CFG); + reg |= CQHCI_ENABLE; + sdhci_writel(host, reg, dwc_priv->vendor_specific_area2 + CQHCI_CFG); +} + +static void rk35xx_sdhci_cqe_enable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + u32 reg; + + reg = sdhci_readl(host, SDHCI_PRESENT_STATE); + while (reg & SDHCI_DATA_AVAILABLE) { + sdhci_readl(host, SDHCI_BUFFER); + reg = sdhci_readl(host, SDHCI_PRESENT_STATE); + } + + sdhci_writew(host, DWCMSHC_SDHCI_CQE_TRNS_MODE, SDHCI_TRANSFER_MODE); + + sdhci_cqe_enable(mmc); +} + +static void rk35xx_sdhci_cqe_disable(struct mmc_host *mmc, bool recovery) +{ + struct sdhci_host *host = mmc_priv(mmc); + unsigned long flags; + u32 ctrl; + + /* + * During CQE command transfers, command complete bit gets latched. + * So s/w should clear command complete interrupt status when CQE is + * either halted or disabled. Otherwise unexpected SDCHI legacy + * interrupt gets triggered when CQE is halted/disabled. + */ + spin_lock_irqsave(&host->lock, flags); + ctrl = sdhci_readl(host, SDHCI_INT_ENABLE); + ctrl |= SDHCI_INT_RESPONSE; + sdhci_writel(host, ctrl, SDHCI_INT_ENABLE); + sdhci_writel(host, SDHCI_INT_RESPONSE, SDHCI_INT_STATUS); + spin_unlock_irqrestore(&host->lock, flags); + + sdhci_cqe_disable(mmc, recovery); +} + +static void rk35xx_sdhci_cqe_post_disable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct dwcmshc_priv *dwc_priv = sdhci_pltfm_priv(pltfm_host); + u32 ctrl; + + ctrl = sdhci_readl(host, dwc_priv->vendor_specific_area2 + CQHCI_CFG); + ctrl &= ~CQHCI_ENABLE; + sdhci_writel(host, ctrl, dwc_priv->vendor_specific_area2 + CQHCI_CFG); +} + static void dwcmshc_rk3568_set_clock(struct sdhci_host *host, unsigned int clock) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -738,6 +804,10 @@ static void rk35xx_sdhci_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); struct rk35xx_priv *priv = dwc_priv->priv; + u32 extra = sdhci_readl(host, DECMSHC_EMMC_MISC_CON); + + if ((host->mmc->caps2 & MMC_CAP2_CQE) && (mask & SDHCI_RESET_ALL)) + cqhci_deactivate(host->mmc); if (mask & SDHCI_RESET_ALL && priv->reset) { reset_control_assert(priv->reset); @@ -746,6 +816,9 @@ static void rk35xx_sdhci_reset(struct sdhci_host *host, u8 mask) } sdhci_reset(host, mask); + + /* Enable INTERNAL CLOCK */ + sdhci_writel(host, MISC_INTCLK_EN | extra, DECMSHC_EMMC_MISC_CON); } static int dwcmshc_rk35xx_init(struct device *dev, struct sdhci_host *host, @@ -1664,6 +1737,15 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_bf3_pdata = { }; #endif +static const struct cqhci_host_ops rk35xx_cqhci_ops = { + .pre_enable = rk35xx_sdhci_cqe_pre_enable, + .enable = rk35xx_sdhci_cqe_enable, + .disable = rk35xx_sdhci_cqe_disable, + .post_disable = rk35xx_sdhci_cqe_post_disable, + .dumpregs = dwcmshc_cqhci_dumpregs, + .set_tran_desc = dwcmshc_set_tran_desc, +}; + static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = { .pdata = { .ops = &sdhci_dwcmshc_rk35xx_ops, @@ -1672,6 +1754,7 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = { .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, }, + .cqhci_host_ops = &rk35xx_cqhci_ops, .init = dwcmshc_rk35xx_init, .postinit = dwcmshc_rk35xx_postinit, }; @@ -1732,7 +1815,8 @@ static const struct cqhci_host_ops dwcmshc_cqhci_ops = { .set_tran_desc = dwcmshc_set_tran_desc, }; -static void dwcmshc_cqhci_init(struct sdhci_host *host, struct platform_device *pdev) +static void dwcmshc_cqhci_init(struct sdhci_host *host, struct platform_device *pdev, + const struct dwcmshc_pltfm_data *pltfm_data) { struct cqhci_host *cq_host; struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -1762,7 +1846,10 @@ static void dwcmshc_cqhci_init(struct sdhci_host *host, struct platform_device * } cq_host->mmio = host->ioaddr + priv->vendor_specific_area2; - cq_host->ops = &dwcmshc_cqhci_ops; + if (pltfm_data->cqhci_host_ops) + cq_host->ops = pltfm_data->cqhci_host_ops; + else + cq_host->ops = &dwcmshc_cqhci_ops; /* Enable using of 128-bit task descriptors */ dma64 = host->flags & SDHCI_USE_64_BIT_DMA; @@ -1934,7 +2021,7 @@ static int dwcmshc_probe(struct platform_device *pdev) priv->vendor_specific_area2 = sdhci_readw(host, DWCMSHC_P_VENDOR_AREA2); - dwcmshc_cqhci_init(host, pdev); + dwcmshc_cqhci_init(host, pdev, pltfm_data); } if (pltfm_data->postinit)