rockchip: backport eMMC CQE support
authorTianling Shen <[email protected]>
Fri, 14 Nov 2025 10:37:40 +0000 (18:37 +0800)
committerRobert Marko <[email protected]>
Tue, 18 Nov 2025 16:26:45 +0000 (17:26 +0100)
Backport eMMC Command Queuing support for RK3576/RK3588.

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.

Signed-off-by: Tianling Shen <[email protected]>
Link: https://github.com/openwrt/openwrt/pull/20780
Signed-off-by: Robert Marko <[email protected]>
target/linux/rockchip/patches-6.12/011-v6.19-arm64-dts-rockchip-add-eMMC-CQE-support-for-rk3588.patch [new file with mode: 0644]
target/linux/rockchip/patches-6.12/037-04-v6.19-mmc-sdhci-of-dwcmshc-Add-command-queue-support-for-rockch.patch [new file with mode: 0644]

diff --git a/target/linux/rockchip/patches-6.12/011-v6.19-arm64-dts-rockchip-add-eMMC-CQE-support-for-rk3588.patch b/target/linux/rockchip/patches-6.12/011-v6.19-arm64-dts-rockchip-add-eMMC-CQE-support-for-rk3588.patch
new file mode 100644 (file)
index 0000000..f50f269
--- /dev/null
@@ -0,0 +1,25 @@
+From 9d856aa1c81930a5d8df0e29d6cb0faa3fa87206 Mon Sep 17 00:00:00 2001
+From: Sebastian Reichel <[email protected]>
+Date: Fri, 31 Oct 2025 16:58:24 +0100
+Subject: [PATCH] arm64: dts: rockchip: add eMMC CQE support for rk3588
+
+The RK3588 eMMC controller supports CQE, so add the missing
+DT flag.
+
+Signed-off-by: Sebastian Reichel <[email protected]>
+Link: https://patch.msgid.link/20251031-rockchip-emmc-cqe-support-v2-2-958171f5edad@collabora.com
+Signed-off-by: Heiko Stuebner <[email protected]>
+---
+ arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
++++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
+@@ -1935,6 +1935,7 @@
+                        <&cru SRST_A_EMMC>, <&cru SRST_B_EMMC>,
+                        <&cru SRST_T_EMMC>;
+               reset-names = "core", "bus", "axi", "block", "timer";
++              supports-cqe;
+               status = "disabled";
+       };
diff --git a/target/linux/rockchip/patches-6.12/037-04-v6.19-mmc-sdhci-of-dwcmshc-Add-command-queue-support-for-rockch.patch b/target/linux/rockchip/patches-6.12/037-04-v6.19-mmc-sdhci-of-dwcmshc-Add-command-queue-support-for-rockch.patch
new file mode 100644 (file)
index 0000000..e7b1c46
--- /dev/null
@@ -0,0 +1,195 @@
+From fda1e0af7c28f96d4f33e57cf51565b0e9c14e63 Mon Sep 17 00:00:00 2001
+From: Sebastian Reichel <[email protected]>
+Date: Fri, 31 Oct 2025 16:58:23 +0100
+Subject: [PATCH] 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 <[email protected]>
+Signed-off-by: Yifeng Zhao <[email protected]>
+Signed-off-by: Sebastian Reichel <[email protected]>
+Acked-by: Adrian Hunter <[email protected]>
+Signed-off-by: Ulf Hansson <[email protected]>
+---
+ drivers/mmc/host/sdhci-of-dwcmshc.c | 93 ++++++++++++++++++++++++++++-
+ 1 file changed, 90 insertions(+), 3 deletions(-)
+
+--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
++++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
+@@ -24,6 +24,7 @@
+ #include "sdhci-pltfm.h"
+ #include "cqhci.h"
++#include "sdhci-cqhci.h"
+ #define SDHCI_DWCMSHC_ARG2_STUFF      GENMASK(31, 16)
+@@ -82,6 +83,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)
+@@ -234,6 +237,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);
+ };
+@@ -603,6 +607,68 @@ static void dwcmshc_cqhci_dumpregs(struc
+       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);
+@@ -721,6 +787,10 @@ static void rk35xx_sdhci_reset(struct sd
+       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);
+@@ -729,6 +799,9 @@ static void rk35xx_sdhci_reset(struct sd
+       }
+       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,
+@@ -1230,6 +1303,15 @@ static const struct dwcmshc_pltfm_data s
+ };
+ #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,
+@@ -1238,6 +1320,7 @@ static const struct dwcmshc_pltfm_data s
+               .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,
+ };
+@@ -1287,7 +1370,8 @@ static const struct cqhci_host_ops dwcms
+       .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);
+@@ -1317,7 +1401,10 @@ static void dwcmshc_cqhci_init(struct sd
+       }
+       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;
+@@ -1486,7 +1573,7 @@ static int dwcmshc_probe(struct platform
+               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)