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