ca1219c0a7
Commit a44f7cb93732 ("mmc: core: use mrq->sbc when sending CMD23 for RPMB") began to use ACMD23 for RPMB if the host supports ACMD23. In RPMB ACM23 case, we need to set bit 31 to CMD23 argument, otherwise RPMB write operation will return general fail. However, no matter V4 is enabled or not, the dwcmshc's ARGUMENT2 register is 32-bit block count register which doesn't support stuff bits of CMD23 argument. So let's handle this specific ACMD23 case. From another side, this patch also prepare for future v4 enabling for dwcmshc, because from the 4.10 spec, the ARGUMENT2 register is redefined as 32bit block count which doesn't support stuff bits of CMD23 argument. Fixes: a44f7cb93732 ("mmc: core: use mrq->sbc when sending CMD23 for RPMB") Signed-off-by: Jisheng Zhang <Jisheng.Zhang@synaptics.com> Acked-by: Adrian Hunter <adrian.hunter@intel.com> Link: https://lore.kernel.org/r/20201229161625.38255233@xhacker.debian Cc: stable@vger.kernel.org Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
256 lines
6.3 KiB
C
256 lines
6.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Driver for Synopsys DesignWare Cores Mobile Storage Host Controller
|
|
*
|
|
* Copyright (C) 2018 Synaptics Incorporated
|
|
*
|
|
* Author: Jisheng Zhang <jszhang@kernel.org>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/sizes.h>
|
|
|
|
#include "sdhci-pltfm.h"
|
|
|
|
#define SDHCI_DWCMSHC_ARG2_STUFF GENMASK(31, 16)
|
|
|
|
/* DWCMSHC specific Mode Select value */
|
|
#define DWCMSHC_CTRL_HS400 0x7
|
|
|
|
#define BOUNDARY_OK(addr, len) \
|
|
((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1)))
|
|
|
|
struct dwcmshc_priv {
|
|
struct clk *bus_clk;
|
|
};
|
|
|
|
/*
|
|
* If DMA addr spans 128MB boundary, we split the DMA transfer into two
|
|
* so that each DMA transfer doesn't exceed the boundary.
|
|
*/
|
|
static void dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc,
|
|
dma_addr_t addr, int len, unsigned int cmd)
|
|
{
|
|
int tmplen, offset;
|
|
|
|
if (likely(!len || BOUNDARY_OK(addr, len))) {
|
|
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
|
return;
|
|
}
|
|
|
|
offset = addr & (SZ_128M - 1);
|
|
tmplen = SZ_128M - offset;
|
|
sdhci_adma_write_desc(host, desc, addr, tmplen, cmd);
|
|
|
|
addr += tmplen;
|
|
len -= tmplen;
|
|
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
|
}
|
|
|
|
static void dwcmshc_check_auto_cmd23(struct mmc_host *mmc,
|
|
struct mmc_request *mrq)
|
|
{
|
|
struct sdhci_host *host = mmc_priv(mmc);
|
|
|
|
/*
|
|
* No matter V4 is enabled or not, ARGUMENT2 register is 32-bit
|
|
* block count register which doesn't support stuff bits of
|
|
* CMD23 argument on dwcmsch host controller.
|
|
*/
|
|
if (mrq->sbc && (mrq->sbc->arg & SDHCI_DWCMSHC_ARG2_STUFF))
|
|
host->flags &= ~SDHCI_AUTO_CMD23;
|
|
else
|
|
host->flags |= SDHCI_AUTO_CMD23;
|
|
}
|
|
|
|
static void dwcmshc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
{
|
|
dwcmshc_check_auto_cmd23(mmc, mrq);
|
|
|
|
sdhci_request(mmc, mrq);
|
|
}
|
|
|
|
static void dwcmshc_set_uhs_signaling(struct sdhci_host *host,
|
|
unsigned int timing)
|
|
{
|
|
u16 ctrl_2;
|
|
|
|
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
|
|
/* Select Bus Speed Mode for host */
|
|
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
|
|
if ((timing == MMC_TIMING_MMC_HS200) ||
|
|
(timing == MMC_TIMING_UHS_SDR104))
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
|
|
else if (timing == MMC_TIMING_UHS_SDR12)
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
|
|
else if ((timing == MMC_TIMING_UHS_SDR25) ||
|
|
(timing == MMC_TIMING_MMC_HS))
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
|
|
else if (timing == MMC_TIMING_UHS_SDR50)
|
|
ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
|
|
else if ((timing == MMC_TIMING_UHS_DDR50) ||
|
|
(timing == MMC_TIMING_MMC_DDR52))
|
|
ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
|
|
else if (timing == MMC_TIMING_MMC_HS400)
|
|
ctrl_2 |= DWCMSHC_CTRL_HS400;
|
|
sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
|
|
}
|
|
|
|
static const struct sdhci_ops sdhci_dwcmshc_ops = {
|
|
.set_clock = sdhci_set_clock,
|
|
.set_bus_width = sdhci_set_bus_width,
|
|
.set_uhs_signaling = dwcmshc_set_uhs_signaling,
|
|
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
|
.reset = sdhci_reset,
|
|
.adma_write_desc = dwcmshc_adma_write_desc,
|
|
};
|
|
|
|
static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
|
|
.ops = &sdhci_dwcmshc_ops,
|
|
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
|
};
|
|
|
|
static int dwcmshc_probe(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_pltfm_host *pltfm_host;
|
|
struct sdhci_host *host;
|
|
struct dwcmshc_priv *priv;
|
|
int err;
|
|
u32 extra;
|
|
|
|
host = sdhci_pltfm_init(pdev, &sdhci_dwcmshc_pdata,
|
|
sizeof(struct dwcmshc_priv));
|
|
if (IS_ERR(host))
|
|
return PTR_ERR(host);
|
|
|
|
/*
|
|
* extra adma table cnt for cross 128M boundary handling.
|
|
*/
|
|
extra = DIV_ROUND_UP_ULL(dma_get_required_mask(&pdev->dev), SZ_128M);
|
|
if (extra > SDHCI_MAX_SEGS)
|
|
extra = SDHCI_MAX_SEGS;
|
|
host->adma_table_cnt += extra;
|
|
|
|
pltfm_host = sdhci_priv(host);
|
|
priv = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
pltfm_host->clk = devm_clk_get(&pdev->dev, "core");
|
|
if (IS_ERR(pltfm_host->clk)) {
|
|
err = PTR_ERR(pltfm_host->clk);
|
|
dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
|
|
goto free_pltfm;
|
|
}
|
|
err = clk_prepare_enable(pltfm_host->clk);
|
|
if (err)
|
|
goto free_pltfm;
|
|
|
|
priv->bus_clk = devm_clk_get(&pdev->dev, "bus");
|
|
if (!IS_ERR(priv->bus_clk))
|
|
clk_prepare_enable(priv->bus_clk);
|
|
|
|
err = mmc_of_parse(host->mmc);
|
|
if (err)
|
|
goto err_clk;
|
|
|
|
sdhci_get_of_property(pdev);
|
|
|
|
host->mmc_host_ops.request = dwcmshc_request;
|
|
|
|
err = sdhci_add_host(host);
|
|
if (err)
|
|
goto err_clk;
|
|
|
|
return 0;
|
|
|
|
err_clk:
|
|
clk_disable_unprepare(pltfm_host->clk);
|
|
clk_disable_unprepare(priv->bus_clk);
|
|
free_pltfm:
|
|
sdhci_pltfm_free(pdev);
|
|
return err;
|
|
}
|
|
|
|
static int dwcmshc_remove(struct platform_device *pdev)
|
|
{
|
|
struct sdhci_host *host = platform_get_drvdata(pdev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
|
|
|
sdhci_remove_host(host, 0);
|
|
|
|
clk_disable_unprepare(pltfm_host->clk);
|
|
clk_disable_unprepare(priv->bus_clk);
|
|
|
|
sdhci_pltfm_free(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int dwcmshc_suspend(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
|
int ret;
|
|
|
|
ret = sdhci_suspend_host(host);
|
|
if (ret)
|
|
return ret;
|
|
|
|
clk_disable_unprepare(pltfm_host->clk);
|
|
if (!IS_ERR(priv->bus_clk))
|
|
clk_disable_unprepare(priv->bus_clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dwcmshc_resume(struct device *dev)
|
|
{
|
|
struct sdhci_host *host = dev_get_drvdata(dev);
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(pltfm_host->clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!IS_ERR(priv->bus_clk)) {
|
|
ret = clk_prepare_enable(priv->bus_clk);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return sdhci_resume_host(host);
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(dwcmshc_pmops, dwcmshc_suspend, dwcmshc_resume);
|
|
|
|
static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
|
|
{ .compatible = "snps,dwcmshc-sdhci" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids);
|
|
|
|
static struct platform_driver sdhci_dwcmshc_driver = {
|
|
.driver = {
|
|
.name = "sdhci-dwcmshc",
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
.of_match_table = sdhci_dwcmshc_dt_ids,
|
|
.pm = &dwcmshc_pmops,
|
|
},
|
|
.probe = dwcmshc_probe,
|
|
.remove = dwcmshc_remove,
|
|
};
|
|
module_platform_driver(sdhci_dwcmshc_driver);
|
|
|
|
MODULE_DESCRIPTION("SDHCI platform driver for Synopsys DWC MSHC");
|
|
MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
|
|
MODULE_LICENSE("GPL v2");
|