Commit fda1e0af authored by Sebastian Reichel's avatar Sebastian Reichel Committed by Ulf Hansson
Browse files

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: default avatarYifeng Zhao <yifeng.zhao@rock-chips.com>
Signed-off-by: default avatarYifeng Zhao <yifeng.zhao@rock-chips.com>
Signed-off-by: default avatarSebastian Reichel <sebastian.reichel@collabora.com>
Acked-by: default avatarAdrian Hunter <adrian.hunter@intel.com>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent eadea8e1
Loading
Loading
Loading
Loading
+90 −3
Original line number Diff line number Diff line
@@ -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,6 +1846,9 @@ static void dwcmshc_cqhci_init(struct sdhci_host *host, struct platform_device *
	}

	cq_host->mmio = host->ioaddr + priv->vendor_specific_area2;
	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 */
@@ -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)