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 19b85c997dSJisheng Zhang #define BOUNDARY_OK(addr, len) \ 20b85c997dSJisheng Zhang ((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1))) 21b85c997dSJisheng Zhang 22e438cf49SJisheng Zhang struct dwcmshc_priv { 23e438cf49SJisheng Zhang struct clk *bus_clk; 24e438cf49SJisheng Zhang }; 25e438cf49SJisheng Zhang 26b85c997dSJisheng Zhang /* 27b85c997dSJisheng Zhang * If DMA addr spans 128MB boundary, we split the DMA transfer into two 28b85c997dSJisheng Zhang * so that each DMA transfer doesn't exceed the boundary. 29b85c997dSJisheng Zhang */ 30b85c997dSJisheng Zhang static void dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc, 31b85c997dSJisheng Zhang dma_addr_t addr, int len, unsigned int cmd) 32b85c997dSJisheng Zhang { 33b85c997dSJisheng Zhang int tmplen, offset; 34b85c997dSJisheng Zhang 35b85c997dSJisheng Zhang if (likely(!len || BOUNDARY_OK(addr, len))) { 36b85c997dSJisheng Zhang sdhci_adma_write_desc(host, desc, addr, len, cmd); 37b85c997dSJisheng Zhang return; 38b85c997dSJisheng Zhang } 39b85c997dSJisheng Zhang 40b85c997dSJisheng Zhang offset = addr & (SZ_128M - 1); 41b85c997dSJisheng Zhang tmplen = SZ_128M - offset; 42b85c997dSJisheng Zhang sdhci_adma_write_desc(host, desc, addr, tmplen, cmd); 43b85c997dSJisheng Zhang 44b85c997dSJisheng Zhang addr += tmplen; 45b85c997dSJisheng Zhang len -= tmplen; 46b85c997dSJisheng Zhang sdhci_adma_write_desc(host, desc, addr, len, cmd); 47b85c997dSJisheng Zhang } 48b85c997dSJisheng Zhang 49e438cf49SJisheng Zhang static const struct sdhci_ops sdhci_dwcmshc_ops = { 50e438cf49SJisheng Zhang .set_clock = sdhci_set_clock, 51e438cf49SJisheng Zhang .set_bus_width = sdhci_set_bus_width, 52e438cf49SJisheng Zhang .set_uhs_signaling = sdhci_set_uhs_signaling, 53e438cf49SJisheng Zhang .get_max_clock = sdhci_pltfm_clk_get_max_clock, 54e438cf49SJisheng Zhang .reset = sdhci_reset, 55b85c997dSJisheng Zhang .adma_write_desc = dwcmshc_adma_write_desc, 56e438cf49SJisheng Zhang }; 57e438cf49SJisheng Zhang 58e438cf49SJisheng Zhang static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = { 59e438cf49SJisheng Zhang .ops = &sdhci_dwcmshc_ops, 60e438cf49SJisheng Zhang .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, 61e438cf49SJisheng Zhang }; 62e438cf49SJisheng Zhang 63e438cf49SJisheng Zhang static int dwcmshc_probe(struct platform_device *pdev) 64e438cf49SJisheng Zhang { 65e438cf49SJisheng Zhang struct sdhci_pltfm_host *pltfm_host; 66e438cf49SJisheng Zhang struct sdhci_host *host; 67e438cf49SJisheng Zhang struct dwcmshc_priv *priv; 68e438cf49SJisheng Zhang int err; 69b85c997dSJisheng Zhang u32 extra; 70e438cf49SJisheng Zhang 71e438cf49SJisheng Zhang host = sdhci_pltfm_init(pdev, &sdhci_dwcmshc_pdata, 72e438cf49SJisheng Zhang sizeof(struct dwcmshc_priv)); 73e438cf49SJisheng Zhang if (IS_ERR(host)) 74e438cf49SJisheng Zhang return PTR_ERR(host); 75e438cf49SJisheng Zhang 76b85c997dSJisheng Zhang /* 77b85c997dSJisheng Zhang * extra adma table cnt for cross 128M boundary handling. 78b85c997dSJisheng Zhang */ 79b85c997dSJisheng Zhang extra = DIV_ROUND_UP_ULL(dma_get_required_mask(&pdev->dev), SZ_128M); 80b85c997dSJisheng Zhang if (extra > SDHCI_MAX_SEGS) 81b85c997dSJisheng Zhang extra = SDHCI_MAX_SEGS; 82b85c997dSJisheng Zhang host->adma_table_cnt += extra; 83b85c997dSJisheng Zhang 84e438cf49SJisheng Zhang pltfm_host = sdhci_priv(host); 85e438cf49SJisheng Zhang priv = sdhci_pltfm_priv(pltfm_host); 86e438cf49SJisheng Zhang 87e438cf49SJisheng Zhang pltfm_host->clk = devm_clk_get(&pdev->dev, "core"); 88e438cf49SJisheng Zhang if (IS_ERR(pltfm_host->clk)) { 89e438cf49SJisheng Zhang err = PTR_ERR(pltfm_host->clk); 90e438cf49SJisheng Zhang dev_err(&pdev->dev, "failed to get core clk: %d\n", err); 91e438cf49SJisheng Zhang goto free_pltfm; 92e438cf49SJisheng Zhang } 93e438cf49SJisheng Zhang err = clk_prepare_enable(pltfm_host->clk); 94e438cf49SJisheng Zhang if (err) 95e438cf49SJisheng Zhang goto free_pltfm; 96e438cf49SJisheng Zhang 97e438cf49SJisheng Zhang priv->bus_clk = devm_clk_get(&pdev->dev, "bus"); 98e438cf49SJisheng Zhang if (!IS_ERR(priv->bus_clk)) 99e438cf49SJisheng Zhang clk_prepare_enable(priv->bus_clk); 100e438cf49SJisheng Zhang 101e438cf49SJisheng Zhang err = mmc_of_parse(host->mmc); 102e438cf49SJisheng Zhang if (err) 103e438cf49SJisheng Zhang goto err_clk; 104e438cf49SJisheng Zhang 105e438cf49SJisheng Zhang sdhci_get_of_property(pdev); 106e438cf49SJisheng Zhang 107e438cf49SJisheng Zhang err = sdhci_add_host(host); 108e438cf49SJisheng Zhang if (err) 109e438cf49SJisheng Zhang goto err_clk; 110e438cf49SJisheng Zhang 111e438cf49SJisheng Zhang return 0; 112e438cf49SJisheng Zhang 113e438cf49SJisheng Zhang err_clk: 114e438cf49SJisheng Zhang clk_disable_unprepare(pltfm_host->clk); 115e438cf49SJisheng Zhang clk_disable_unprepare(priv->bus_clk); 116e438cf49SJisheng Zhang free_pltfm: 117e438cf49SJisheng Zhang sdhci_pltfm_free(pdev); 118e438cf49SJisheng Zhang return err; 119e438cf49SJisheng Zhang } 120e438cf49SJisheng Zhang 121e438cf49SJisheng Zhang static int dwcmshc_remove(struct platform_device *pdev) 122e438cf49SJisheng Zhang { 123e438cf49SJisheng Zhang struct sdhci_host *host = platform_get_drvdata(pdev); 124e438cf49SJisheng Zhang struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 125e438cf49SJisheng Zhang struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host); 126e438cf49SJisheng Zhang 127e438cf49SJisheng Zhang sdhci_remove_host(host, 0); 128e438cf49SJisheng Zhang 129e438cf49SJisheng Zhang clk_disable_unprepare(pltfm_host->clk); 130e438cf49SJisheng Zhang clk_disable_unprepare(priv->bus_clk); 131e438cf49SJisheng Zhang 132e438cf49SJisheng Zhang sdhci_pltfm_free(pdev); 133e438cf49SJisheng Zhang 134e438cf49SJisheng Zhang return 0; 135e438cf49SJisheng Zhang } 136e438cf49SJisheng Zhang 137e438cf49SJisheng Zhang static const struct of_device_id sdhci_dwcmshc_dt_ids[] = { 138e438cf49SJisheng Zhang { .compatible = "snps,dwcmshc-sdhci" }, 139e438cf49SJisheng Zhang {} 140e438cf49SJisheng Zhang }; 141e438cf49SJisheng Zhang MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids); 142e438cf49SJisheng Zhang 143e438cf49SJisheng Zhang static struct platform_driver sdhci_dwcmshc_driver = { 144e438cf49SJisheng Zhang .driver = { 145e438cf49SJisheng Zhang .name = "sdhci-dwcmshc", 146e438cf49SJisheng Zhang .of_match_table = sdhci_dwcmshc_dt_ids, 147e438cf49SJisheng Zhang }, 148e438cf49SJisheng Zhang .probe = dwcmshc_probe, 149e438cf49SJisheng Zhang .remove = dwcmshc_remove, 150e438cf49SJisheng Zhang }; 151e438cf49SJisheng Zhang module_platform_driver(sdhci_dwcmshc_driver); 152e438cf49SJisheng Zhang 153e438cf49SJisheng Zhang MODULE_DESCRIPTION("SDHCI platform driver for Synopsys DWC MSHC"); 154e438cf49SJisheng Zhang MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>"); 155e438cf49SJisheng Zhang MODULE_LICENSE("GPL v2"); 156