1e438cf49SJisheng Zhang // SPDX-License-Identifier: GPL-2.0 2e438cf49SJisheng Zhang /* 3e438cf49SJisheng Zhang * Driver for Synopsys DesignWare Cores Mobile Storage Host Controller 4e438cf49SJisheng Zhang * 5e438cf49SJisheng Zhang * Copyright (C) 2018 Synaptics Incorporated 6e438cf49SJisheng Zhang * 7e438cf49SJisheng Zhang * Author: Jisheng Zhang <jszhang@kernel.org> 8e438cf49SJisheng Zhang */ 9e438cf49SJisheng Zhang 10e438cf49SJisheng Zhang #include <linux/clk.h> 11b85c997dSJisheng Zhang #include <linux/dma-mapping.h> 12b85c997dSJisheng Zhang #include <linux/kernel.h> 13e438cf49SJisheng Zhang #include <linux/module.h> 14e438cf49SJisheng Zhang #include <linux/of.h> 15b85c997dSJisheng Zhang #include <linux/sizes.h> 16e438cf49SJisheng Zhang 17e438cf49SJisheng Zhang #include "sdhci-pltfm.h" 18e438cf49SJisheng Zhang 19*ca1219c0SJisheng Zhang #define SDHCI_DWCMSHC_ARG2_STUFF GENMASK(31, 16) 20*ca1219c0SJisheng Zhang 21554232e8SJisheng Zhang /* DWCMSHC specific Mode Select value */ 22554232e8SJisheng Zhang #define DWCMSHC_CTRL_HS400 0x7 23554232e8SJisheng Zhang 24b85c997dSJisheng Zhang #define BOUNDARY_OK(addr, len) \ 25b85c997dSJisheng Zhang ((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1))) 26b85c997dSJisheng Zhang 27e438cf49SJisheng Zhang struct dwcmshc_priv { 28e438cf49SJisheng Zhang struct clk *bus_clk; 29e438cf49SJisheng Zhang }; 30e438cf49SJisheng Zhang 31b85c997dSJisheng Zhang /* 32b85c997dSJisheng Zhang * If DMA addr spans 128MB boundary, we split the DMA transfer into two 33b85c997dSJisheng Zhang * so that each DMA transfer doesn't exceed the boundary. 34b85c997dSJisheng Zhang */ 35b85c997dSJisheng Zhang static void dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc, 36b85c997dSJisheng Zhang dma_addr_t addr, int len, unsigned int cmd) 37b85c997dSJisheng Zhang { 38b85c997dSJisheng Zhang int tmplen, offset; 39b85c997dSJisheng Zhang 40b85c997dSJisheng Zhang if (likely(!len || BOUNDARY_OK(addr, len))) { 41b85c997dSJisheng Zhang sdhci_adma_write_desc(host, desc, addr, len, cmd); 42b85c997dSJisheng Zhang return; 43b85c997dSJisheng Zhang } 44b85c997dSJisheng Zhang 45b85c997dSJisheng Zhang offset = addr & (SZ_128M - 1); 46b85c997dSJisheng Zhang tmplen = SZ_128M - offset; 47b85c997dSJisheng Zhang sdhci_adma_write_desc(host, desc, addr, tmplen, cmd); 48b85c997dSJisheng Zhang 49b85c997dSJisheng Zhang addr += tmplen; 50b85c997dSJisheng Zhang len -= tmplen; 51b85c997dSJisheng Zhang sdhci_adma_write_desc(host, desc, addr, len, cmd); 52b85c997dSJisheng Zhang } 53b85c997dSJisheng Zhang 54*ca1219c0SJisheng Zhang static void dwcmshc_check_auto_cmd23(struct mmc_host *mmc, 55*ca1219c0SJisheng Zhang struct mmc_request *mrq) 56*ca1219c0SJisheng Zhang { 57*ca1219c0SJisheng Zhang struct sdhci_host *host = mmc_priv(mmc); 58*ca1219c0SJisheng Zhang 59*ca1219c0SJisheng Zhang /* 60*ca1219c0SJisheng Zhang * No matter V4 is enabled or not, ARGUMENT2 register is 32-bit 61*ca1219c0SJisheng Zhang * block count register which doesn't support stuff bits of 62*ca1219c0SJisheng Zhang * CMD23 argument on dwcmsch host controller. 63*ca1219c0SJisheng Zhang */ 64*ca1219c0SJisheng Zhang if (mrq->sbc && (mrq->sbc->arg & SDHCI_DWCMSHC_ARG2_STUFF)) 65*ca1219c0SJisheng Zhang host->flags &= ~SDHCI_AUTO_CMD23; 66*ca1219c0SJisheng Zhang else 67*ca1219c0SJisheng Zhang host->flags |= SDHCI_AUTO_CMD23; 68*ca1219c0SJisheng Zhang } 69*ca1219c0SJisheng Zhang 70*ca1219c0SJisheng Zhang static void dwcmshc_request(struct mmc_host *mmc, struct mmc_request *mrq) 71*ca1219c0SJisheng Zhang { 72*ca1219c0SJisheng Zhang dwcmshc_check_auto_cmd23(mmc, mrq); 73*ca1219c0SJisheng Zhang 74*ca1219c0SJisheng Zhang sdhci_request(mmc, mrq); 75*ca1219c0SJisheng Zhang } 76*ca1219c0SJisheng Zhang 77554232e8SJisheng Zhang static void dwcmshc_set_uhs_signaling(struct sdhci_host *host, 78554232e8SJisheng Zhang unsigned int timing) 79554232e8SJisheng Zhang { 80554232e8SJisheng Zhang u16 ctrl_2; 81554232e8SJisheng Zhang 82554232e8SJisheng Zhang ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); 83554232e8SJisheng Zhang /* Select Bus Speed Mode for host */ 84554232e8SJisheng Zhang ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; 85554232e8SJisheng Zhang if ((timing == MMC_TIMING_MMC_HS200) || 86554232e8SJisheng Zhang (timing == MMC_TIMING_UHS_SDR104)) 87554232e8SJisheng Zhang ctrl_2 |= SDHCI_CTRL_UHS_SDR104; 88554232e8SJisheng Zhang else if (timing == MMC_TIMING_UHS_SDR12) 89554232e8SJisheng Zhang ctrl_2 |= SDHCI_CTRL_UHS_SDR12; 90554232e8SJisheng Zhang else if ((timing == MMC_TIMING_UHS_SDR25) || 91554232e8SJisheng Zhang (timing == MMC_TIMING_MMC_HS)) 92554232e8SJisheng Zhang ctrl_2 |= SDHCI_CTRL_UHS_SDR25; 93554232e8SJisheng Zhang else if (timing == MMC_TIMING_UHS_SDR50) 94554232e8SJisheng Zhang ctrl_2 |= SDHCI_CTRL_UHS_SDR50; 95554232e8SJisheng Zhang else if ((timing == MMC_TIMING_UHS_DDR50) || 96554232e8SJisheng Zhang (timing == MMC_TIMING_MMC_DDR52)) 97554232e8SJisheng Zhang ctrl_2 |= SDHCI_CTRL_UHS_DDR50; 98554232e8SJisheng Zhang else if (timing == MMC_TIMING_MMC_HS400) 99554232e8SJisheng Zhang ctrl_2 |= DWCMSHC_CTRL_HS400; 100554232e8SJisheng Zhang sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); 101554232e8SJisheng Zhang } 102554232e8SJisheng Zhang 103e438cf49SJisheng Zhang static const struct sdhci_ops sdhci_dwcmshc_ops = { 104e438cf49SJisheng Zhang .set_clock = sdhci_set_clock, 105e438cf49SJisheng Zhang .set_bus_width = sdhci_set_bus_width, 106554232e8SJisheng Zhang .set_uhs_signaling = dwcmshc_set_uhs_signaling, 107e438cf49SJisheng Zhang .get_max_clock = sdhci_pltfm_clk_get_max_clock, 108e438cf49SJisheng Zhang .reset = sdhci_reset, 109b85c997dSJisheng Zhang .adma_write_desc = dwcmshc_adma_write_desc, 110e438cf49SJisheng Zhang }; 111e438cf49SJisheng Zhang 112e438cf49SJisheng Zhang static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = { 113e438cf49SJisheng Zhang .ops = &sdhci_dwcmshc_ops, 114e438cf49SJisheng Zhang .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, 115e438cf49SJisheng Zhang }; 116e438cf49SJisheng Zhang 117e438cf49SJisheng Zhang static int dwcmshc_probe(struct platform_device *pdev) 118e438cf49SJisheng Zhang { 119e438cf49SJisheng Zhang struct sdhci_pltfm_host *pltfm_host; 120e438cf49SJisheng Zhang struct sdhci_host *host; 121e438cf49SJisheng Zhang struct dwcmshc_priv *priv; 122e438cf49SJisheng Zhang int err; 123b85c997dSJisheng Zhang u32 extra; 124e438cf49SJisheng Zhang 125e438cf49SJisheng Zhang host = sdhci_pltfm_init(pdev, &sdhci_dwcmshc_pdata, 126e438cf49SJisheng Zhang sizeof(struct dwcmshc_priv)); 127e438cf49SJisheng Zhang if (IS_ERR(host)) 128e438cf49SJisheng Zhang return PTR_ERR(host); 129e438cf49SJisheng Zhang 130b85c997dSJisheng Zhang /* 131b85c997dSJisheng Zhang * extra adma table cnt for cross 128M boundary handling. 132b85c997dSJisheng Zhang */ 133b85c997dSJisheng Zhang extra = DIV_ROUND_UP_ULL(dma_get_required_mask(&pdev->dev), SZ_128M); 134b85c997dSJisheng Zhang if (extra > SDHCI_MAX_SEGS) 135b85c997dSJisheng Zhang extra = SDHCI_MAX_SEGS; 136b85c997dSJisheng Zhang host->adma_table_cnt += extra; 137b85c997dSJisheng Zhang 138e438cf49SJisheng Zhang pltfm_host = sdhci_priv(host); 139e438cf49SJisheng Zhang priv = sdhci_pltfm_priv(pltfm_host); 140e438cf49SJisheng Zhang 141e438cf49SJisheng Zhang pltfm_host->clk = devm_clk_get(&pdev->dev, "core"); 142e438cf49SJisheng Zhang if (IS_ERR(pltfm_host->clk)) { 143e438cf49SJisheng Zhang err = PTR_ERR(pltfm_host->clk); 144e438cf49SJisheng Zhang dev_err(&pdev->dev, "failed to get core clk: %d\n", err); 145e438cf49SJisheng Zhang goto free_pltfm; 146e438cf49SJisheng Zhang } 147e438cf49SJisheng Zhang err = clk_prepare_enable(pltfm_host->clk); 148e438cf49SJisheng Zhang if (err) 149e438cf49SJisheng Zhang goto free_pltfm; 150e438cf49SJisheng Zhang 151e438cf49SJisheng Zhang priv->bus_clk = devm_clk_get(&pdev->dev, "bus"); 152e438cf49SJisheng Zhang if (!IS_ERR(priv->bus_clk)) 153e438cf49SJisheng Zhang clk_prepare_enable(priv->bus_clk); 154e438cf49SJisheng Zhang 155e438cf49SJisheng Zhang err = mmc_of_parse(host->mmc); 156e438cf49SJisheng Zhang if (err) 157e438cf49SJisheng Zhang goto err_clk; 158e438cf49SJisheng Zhang 159e438cf49SJisheng Zhang sdhci_get_of_property(pdev); 160e438cf49SJisheng Zhang 161*ca1219c0SJisheng Zhang host->mmc_host_ops.request = dwcmshc_request; 162*ca1219c0SJisheng Zhang 163e438cf49SJisheng Zhang err = sdhci_add_host(host); 164e438cf49SJisheng Zhang if (err) 165e438cf49SJisheng Zhang goto err_clk; 166e438cf49SJisheng Zhang 167e438cf49SJisheng Zhang return 0; 168e438cf49SJisheng Zhang 169e438cf49SJisheng Zhang err_clk: 170e438cf49SJisheng Zhang clk_disable_unprepare(pltfm_host->clk); 171e438cf49SJisheng Zhang clk_disable_unprepare(priv->bus_clk); 172e438cf49SJisheng Zhang free_pltfm: 173e438cf49SJisheng Zhang sdhci_pltfm_free(pdev); 174e438cf49SJisheng Zhang return err; 175e438cf49SJisheng Zhang } 176e438cf49SJisheng Zhang 177e438cf49SJisheng Zhang static int dwcmshc_remove(struct platform_device *pdev) 178e438cf49SJisheng Zhang { 179e438cf49SJisheng Zhang struct sdhci_host *host = platform_get_drvdata(pdev); 180e438cf49SJisheng Zhang struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 181e438cf49SJisheng Zhang struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); 182e438cf49SJisheng Zhang 183e438cf49SJisheng Zhang sdhci_remove_host(host, 0); 184e438cf49SJisheng Zhang 185e438cf49SJisheng Zhang clk_disable_unprepare(pltfm_host->clk); 186e438cf49SJisheng Zhang clk_disable_unprepare(priv->bus_clk); 187e438cf49SJisheng Zhang 188e438cf49SJisheng Zhang sdhci_pltfm_free(pdev); 189e438cf49SJisheng Zhang 190e438cf49SJisheng Zhang return 0; 191e438cf49SJisheng Zhang } 192e438cf49SJisheng Zhang 193bccce2ecSJisheng Zhang #ifdef CONFIG_PM_SLEEP 194bccce2ecSJisheng Zhang static int dwcmshc_suspend(struct device *dev) 195bccce2ecSJisheng Zhang { 196bccce2ecSJisheng Zhang struct sdhci_host *host = dev_get_drvdata(dev); 197bccce2ecSJisheng Zhang struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 198bccce2ecSJisheng Zhang struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); 199bccce2ecSJisheng Zhang int ret; 200bccce2ecSJisheng Zhang 201bccce2ecSJisheng Zhang ret = sdhci_suspend_host(host); 202bccce2ecSJisheng Zhang if (ret) 203bccce2ecSJisheng Zhang return ret; 204bccce2ecSJisheng Zhang 205bccce2ecSJisheng Zhang clk_disable_unprepare(pltfm_host->clk); 206bccce2ecSJisheng Zhang if (!IS_ERR(priv->bus_clk)) 207bccce2ecSJisheng Zhang clk_disable_unprepare(priv->bus_clk); 208bccce2ecSJisheng Zhang 209bccce2ecSJisheng Zhang return ret; 210bccce2ecSJisheng Zhang } 211bccce2ecSJisheng Zhang 212bccce2ecSJisheng Zhang static int dwcmshc_resume(struct device *dev) 213bccce2ecSJisheng Zhang { 214bccce2ecSJisheng Zhang struct sdhci_host *host = dev_get_drvdata(dev); 215bccce2ecSJisheng Zhang struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 216bccce2ecSJisheng Zhang struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); 217bccce2ecSJisheng Zhang int ret; 218bccce2ecSJisheng Zhang 219bccce2ecSJisheng Zhang ret = clk_prepare_enable(pltfm_host->clk); 220bccce2ecSJisheng Zhang if (ret) 221bccce2ecSJisheng Zhang return ret; 222bccce2ecSJisheng Zhang 223bccce2ecSJisheng Zhang if (!IS_ERR(priv->bus_clk)) { 224bccce2ecSJisheng Zhang ret = clk_prepare_enable(priv->bus_clk); 225bccce2ecSJisheng Zhang if (ret) 226bccce2ecSJisheng Zhang return ret; 227bccce2ecSJisheng Zhang } 228bccce2ecSJisheng Zhang 229bccce2ecSJisheng Zhang return sdhci_resume_host(host); 230bccce2ecSJisheng Zhang } 231bccce2ecSJisheng Zhang #endif 232bccce2ecSJisheng Zhang 233bccce2ecSJisheng Zhang static SIMPLE_DEV_PM_OPS(dwcmshc_pmops, dwcmshc_suspend, dwcmshc_resume); 234bccce2ecSJisheng Zhang 235e438cf49SJisheng Zhang static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { 236e438cf49SJisheng Zhang { .compatible = "snps,dwcmshc-sdhci" }, 237e438cf49SJisheng Zhang {} 238e438cf49SJisheng Zhang }; 239e438cf49SJisheng Zhang MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids); 240e438cf49SJisheng Zhang 241e438cf49SJisheng Zhang static struct platform_driver sdhci_dwcmshc_driver = { 242e438cf49SJisheng Zhang .driver = { 243e438cf49SJisheng Zhang .name = "sdhci-dwcmshc", 244a1a48919SDouglas Anderson .probe_type = PROBE_PREFER_ASYNCHRONOUS, 245e438cf49SJisheng Zhang .of_match_table = sdhci_dwcmshc_dt_ids, 246bccce2ecSJisheng Zhang .pm = &dwcmshc_pmops, 247e438cf49SJisheng Zhang }, 248e438cf49SJisheng Zhang .probe = dwcmshc_probe, 249e438cf49SJisheng Zhang .remove = dwcmshc_remove, 250e438cf49SJisheng Zhang }; 251e438cf49SJisheng Zhang module_platform_driver(sdhci_dwcmshc_driver); 252e438cf49SJisheng Zhang 253e438cf49SJisheng Zhang MODULE_DESCRIPTION("SDHCI platform driver for Synopsys DWC MSHC"); 254e438cf49SJisheng Zhang MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>"); 255e438cf49SJisheng Zhang MODULE_LICENSE("GPL v2"); 256