diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c index 83fb115bca6d..bcf33a63d353 100644 --- a/drivers/mmc/host/sdhci-of-dwcmshc.c +++ b/drivers/mmc/host/sdhci-of-dwcmshc.c @@ -9,9 +9,11 @@ #include #include +#include #include #include #include +#include #include #include "sdhci-pltfm.h" @@ -19,14 +21,43 @@ /* DWCMSHC specific Mode Select value */ #define DWCMSHC_CTRL_HS400 0x7 +#define DWCMSHC_VER_ID 0x500 +#define DWCMSHC_VER_TYPE 0x504 +#define DWCMSHC_HOST_CTRL3 0x508 #define DWCMSHC_EMMC_CONTROL 0x52c + +/* Rockchip specific Registers */ +#define DWCMSHC_EMMC_DLL_CTRL 0x800 +#define DWCMSHC_EMMC_DLL_TXCLK 0x804 +#define DWCMSHC_EMMC_DLL_RXCLK 0x808 +#define DWCMSHC_EMMC_DLL_STRBIN 0x80c +#define DWCMSHC_EMMC_DLL_STATUS0 0x820 +#define DWCMSHC_EMMC_DLL_START BIT(0) +#define DWCMSHC_EMMC_DLL_RXCLK_SRCSEL 29 +#define DWCMSHC_EMMC_DLL_START_POINT 16 +#define DWCMSHC_EMMC_DLL_INC 8 +#define DWCMSHC_EMMC_DLL_DLYENA BIT(27) +#define DLL_RXCLK_TAPNUM_DEFAULT 0x3 +#define DLL_STRBIN_TAPNUM_DEFAULT 0x3 +#define DLL_RXCLK_TAPNUM_FROM_SW BIT(24) +#define DWCMSHC_EMMC_DLL_LOCKED BIT(8) +#define DWCMSHC_EMMC_DLL_TIMEOUT BIT(9) +#define DLL_RXCLK_NO_INVERTER 1 +#define DLL_RXCLK_INVERTER 0 #define DWCMSHC_ENHANCED_STROBE BIT(8) +#define DLL_LOCK_WO_TMOUT(x) \ + ((((x) & DWCMSHC_EMMC_DLL_LOCKED) == DWCMSHC_EMMC_DLL_LOCKED) && \ + (((x) & DWCMSHC_EMMC_DLL_TIMEOUT) == 0)) +#define ROCKCHIP_MAX_CLKS 3 #define BOUNDARY_OK(addr, len) \ ((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1))) struct dwcmshc_priv { struct clk *bus_clk; + + /* Rockchip specified optional clocks */ + struct clk_bulk_data rockchip_clks[ROCKCHIP_MAX_CLKS]; }; /* @@ -107,15 +138,97 @@ static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = { .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, }; +static const struct sdhci_pltfm_data sdhci_dwcmshc_rk_pdata = { + .ops = &sdhci_dwcmshc_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, +}; + +static int rockchip_pltf_init(struct sdhci_host *host, struct dwcmshc_priv *priv) +{ + int err; + u32 extra; + + priv->rockchip_clks[0].id = "axi"; + priv->rockchip_clks[1].id = "block"; + priv->rockchip_clks[2].id = "timer"; + err = devm_clk_bulk_get_optional(mmc_dev(host->mmc), ROCKCHIP_MAX_CLKS, + priv->rockchip_clks); + if (err) { + dev_err(mmc_dev(host->mmc), "failed to get clocks %d\n", err); + return err; + } + + err = clk_bulk_prepare_enable(ROCKCHIP_MAX_CLKS, priv->rockchip_clks); + if (err) { + dev_err(mmc_dev(host->mmc), "failed to enable clocks %d\n", err); + return err; + } + + /* Reset DLL */ + sdhci_writel(host, BIT(1), DWCMSHC_EMMC_DLL_CTRL); + udelay(1); + sdhci_writel(host, 0x0, DWCMSHC_EMMC_DLL_CTRL); + + /* Init DLL settings */ + extra = 0x5 << DWCMSHC_EMMC_DLL_START_POINT | + 0x2 << DWCMSHC_EMMC_DLL_INC | + DWCMSHC_EMMC_DLL_START; + sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_CTRL); + err = readl_poll_timeout(host->ioaddr + DWCMSHC_EMMC_DLL_STATUS0, + extra, DLL_LOCK_WO_TMOUT(extra), 1, + 500 * USEC_PER_MSEC); + if (err) { + dev_err(mmc_dev(host->mmc), "DLL lock timeout!\n"); + /* FixMe: change start_point or inc ? */ + return -ETIMEDOUT; + } + + /* FixMe: clk inverter? */ + extra = DWCMSHC_EMMC_DLL_DLYENA | + DLL_RXCLK_NO_INVERTER << DWCMSHC_EMMC_DLL_RXCLK_SRCSEL; + sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_RXCLK); + + extra = DWCMSHC_EMMC_DLL_DLYENA | + DLL_RXCLK_TAPNUM_DEFAULT | + DLL_RXCLK_TAPNUM_FROM_SW; + sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_TXCLK); + + extra = DWCMSHC_EMMC_DLL_DLYENA | + DLL_STRBIN_TAPNUM_DEFAULT; + sdhci_writel(host, extra, DWCMSHC_EMMC_DLL_STRBIN); + return 0; +} + +static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { + { + .compatible = "snps,dwcmshc-sdhci", + .data = &sdhci_dwcmshc_pdata, + }, + { + .compatible = "rockchip,dwcmshc-sdhci", + .data = &sdhci_dwcmshc_rk_pdata, + }, + {}, +}; + static int dwcmshc_probe(struct platform_device *pdev) { struct sdhci_pltfm_host *pltfm_host; struct sdhci_host *host; struct dwcmshc_priv *priv; + const struct sdhci_pltfm_data *pltfm_data; int err; u32 extra; - host = sdhci_pltfm_init(pdev, &sdhci_dwcmshc_pdata, + pltfm_data = of_device_get_match_data(&pdev->dev); + if (!pltfm_data) { + dev_err(&pdev->dev, "Error: No device match data found\n"); + return -ENODEV; + } + + host = sdhci_pltfm_init(pdev, pltfm_data, sizeof(struct dwcmshc_priv)); if (IS_ERR(host)) return PTR_ERR(host); @@ -154,6 +267,12 @@ static int dwcmshc_probe(struct platform_device *pdev) host->mmc_host_ops.hs400_enhanced_strobe = dwcmshc_hs400_enhanced_strobe; + if (pltfm_data == &sdhci_dwcmshc_rk_pdata) { + err = rockchip_pltf_init(host, priv); + if (err) + goto err_clk; + } + err = sdhci_add_host(host); if (err) goto err_clk; @@ -163,6 +282,7 @@ static int dwcmshc_probe(struct platform_device *pdev) err_clk: clk_disable_unprepare(pltfm_host->clk); clk_disable_unprepare(priv->bus_clk); + clk_bulk_disable_unprepare(ROCKCHIP_MAX_CLKS, priv->rockchip_clks); free_pltfm: sdhci_pltfm_free(pdev); return err; @@ -178,6 +298,7 @@ static int dwcmshc_remove(struct platform_device *pdev) clk_disable_unprepare(pltfm_host->clk); clk_disable_unprepare(priv->bus_clk); + clk_bulk_disable_unprepare(ROCKCHIP_MAX_CLKS, priv->rockchip_clks); sdhci_pltfm_free(pdev); @@ -200,6 +321,7 @@ static int dwcmshc_suspend(struct device *dev) if (!IS_ERR(priv->bus_clk)) clk_disable_unprepare(priv->bus_clk); + clk_bulk_disable_unprepare(ROCKCHIP_MAX_CLKS, priv->rockchip_clks); return ret; } @@ -220,16 +342,16 @@ static int dwcmshc_resume(struct device *dev) return ret; } + ret = clk_bulk_prepare_enable(ROCKCHIP_MAX_CLKS, priv->rockchip_clks); + 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 = {