1 // SPDX-License-Identifier: GPL-2.0
3 * Driver for Synopsys DesignWare Cores Mobile Storage Host Controller
5 * Copyright (C) 2018 Synaptics Incorporated
7 * Author: Jisheng Zhang <jszhang@kernel.org>
10 #include <linux/clk.h>
11 #include <linux/dma-mapping.h>
12 #include <linux/kernel.h>
13 #include <linux/module.h>
15 #include <linux/sizes.h>
17 #include "sdhci-pltfm.h"
19 /* DWCMSHC specific Mode Select value */
20 #define DWCMSHC_CTRL_HS400 0x7
22 #define BOUNDARY_OK(addr, len) \
23 ((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1)))
30 * If DMA addr spans 128MB boundary, we split the DMA transfer into two
31 * so that each DMA transfer doesn't exceed the boundary.
33 static void dwcmshc_adma_write_desc(struct sdhci_host
*host
, void **desc
,
34 dma_addr_t addr
, int len
, unsigned int cmd
)
38 if (likely(!len
|| BOUNDARY_OK(addr
, len
))) {
39 sdhci_adma_write_desc(host
, desc
, addr
, len
, cmd
);
43 offset
= addr
& (SZ_128M
- 1);
44 tmplen
= SZ_128M
- offset
;
45 sdhci_adma_write_desc(host
, desc
, addr
, tmplen
, cmd
);
49 sdhci_adma_write_desc(host
, desc
, addr
, len
, cmd
);
52 static void dwcmshc_set_uhs_signaling(struct sdhci_host
*host
,
57 ctrl_2
= sdhci_readw(host
, SDHCI_HOST_CONTROL2
);
58 /* Select Bus Speed Mode for host */
59 ctrl_2
&= ~SDHCI_CTRL_UHS_MASK
;
60 if ((timing
== MMC_TIMING_MMC_HS200
) ||
61 (timing
== MMC_TIMING_UHS_SDR104
))
62 ctrl_2
|= SDHCI_CTRL_UHS_SDR104
;
63 else if (timing
== MMC_TIMING_UHS_SDR12
)
64 ctrl_2
|= SDHCI_CTRL_UHS_SDR12
;
65 else if ((timing
== MMC_TIMING_UHS_SDR25
) ||
66 (timing
== MMC_TIMING_MMC_HS
))
67 ctrl_2
|= SDHCI_CTRL_UHS_SDR25
;
68 else if (timing
== MMC_TIMING_UHS_SDR50
)
69 ctrl_2
|= SDHCI_CTRL_UHS_SDR50
;
70 else if ((timing
== MMC_TIMING_UHS_DDR50
) ||
71 (timing
== MMC_TIMING_MMC_DDR52
))
72 ctrl_2
|= SDHCI_CTRL_UHS_DDR50
;
73 else if (timing
== MMC_TIMING_MMC_HS400
)
74 ctrl_2
|= DWCMSHC_CTRL_HS400
;
75 sdhci_writew(host
, ctrl_2
, SDHCI_HOST_CONTROL2
);
78 static const struct sdhci_ops sdhci_dwcmshc_ops
= {
79 .set_clock
= sdhci_set_clock
,
80 .set_bus_width
= sdhci_set_bus_width
,
81 .set_uhs_signaling
= dwcmshc_set_uhs_signaling
,
82 .get_max_clock
= sdhci_pltfm_clk_get_max_clock
,
84 .adma_write_desc
= dwcmshc_adma_write_desc
,
87 static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata
= {
88 .ops
= &sdhci_dwcmshc_ops
,
89 .quirks
= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN
,
92 static int dwcmshc_probe(struct platform_device
*pdev
)
94 struct sdhci_pltfm_host
*pltfm_host
;
95 struct sdhci_host
*host
;
96 struct dwcmshc_priv
*priv
;
100 host
= sdhci_pltfm_init(pdev
, &sdhci_dwcmshc_pdata
,
101 sizeof(struct dwcmshc_priv
));
103 return PTR_ERR(host
);
106 * extra adma table cnt for cross 128M boundary handling.
108 extra
= DIV_ROUND_UP_ULL(dma_get_required_mask(&pdev
->dev
), SZ_128M
);
109 if (extra
> SDHCI_MAX_SEGS
)
110 extra
= SDHCI_MAX_SEGS
;
111 host
->adma_table_cnt
+= extra
;
113 pltfm_host
= sdhci_priv(host
);
114 priv
= sdhci_pltfm_priv(pltfm_host
);
116 pltfm_host
->clk
= devm_clk_get(&pdev
->dev
, "core");
117 if (IS_ERR(pltfm_host
->clk
)) {
118 err
= PTR_ERR(pltfm_host
->clk
);
119 dev_err(&pdev
->dev
, "failed to get core clk: %d\n", err
);
122 err
= clk_prepare_enable(pltfm_host
->clk
);
126 priv
->bus_clk
= devm_clk_get(&pdev
->dev
, "bus");
127 if (!IS_ERR(priv
->bus_clk
))
128 clk_prepare_enable(priv
->bus_clk
);
130 err
= mmc_of_parse(host
->mmc
);
134 sdhci_get_of_property(pdev
);
136 err
= sdhci_add_host(host
);
143 clk_disable_unprepare(pltfm_host
->clk
);
144 clk_disable_unprepare(priv
->bus_clk
);
146 sdhci_pltfm_free(pdev
);
150 static int dwcmshc_remove(struct platform_device
*pdev
)
152 struct sdhci_host
*host
= platform_get_drvdata(pdev
);
153 struct sdhci_pltfm_host
*pltfm_host
= sdhci_priv(host
);
154 struct dwcmshc_priv
*priv
= sdhci_pltfm_priv(pltfm_host
);
156 sdhci_remove_host(host
, 0);
158 clk_disable_unprepare(pltfm_host
->clk
);
159 clk_disable_unprepare(priv
->bus_clk
);
161 sdhci_pltfm_free(pdev
);
166 #ifdef CONFIG_PM_SLEEP
167 static int dwcmshc_suspend(struct device
*dev
)
169 struct sdhci_host
*host
= dev_get_drvdata(dev
);
170 struct sdhci_pltfm_host
*pltfm_host
= sdhci_priv(host
);
171 struct dwcmshc_priv
*priv
= sdhci_pltfm_priv(pltfm_host
);
174 ret
= sdhci_suspend_host(host
);
178 clk_disable_unprepare(pltfm_host
->clk
);
179 if (!IS_ERR(priv
->bus_clk
))
180 clk_disable_unprepare(priv
->bus_clk
);
185 static int dwcmshc_resume(struct device
*dev
)
187 struct sdhci_host
*host
= dev_get_drvdata(dev
);
188 struct sdhci_pltfm_host
*pltfm_host
= sdhci_priv(host
);
189 struct dwcmshc_priv
*priv
= sdhci_pltfm_priv(pltfm_host
);
192 ret
= clk_prepare_enable(pltfm_host
->clk
);
196 if (!IS_ERR(priv
->bus_clk
)) {
197 ret
= clk_prepare_enable(priv
->bus_clk
);
202 return sdhci_resume_host(host
);
206 static SIMPLE_DEV_PM_OPS(dwcmshc_pmops
, dwcmshc_suspend
, dwcmshc_resume
);
208 static const struct of_device_id sdhci_dwcmshc_dt_ids
[] = {
209 { .compatible
= "snps,dwcmshc-sdhci" },
212 MODULE_DEVICE_TABLE(of
, sdhci_dwcmshc_dt_ids
);
214 static struct platform_driver sdhci_dwcmshc_driver
= {
216 .name
= "sdhci-dwcmshc",
217 .probe_type
= PROBE_PREFER_ASYNCHRONOUS
,
218 .of_match_table
= sdhci_dwcmshc_dt_ids
,
219 .pm
= &dwcmshc_pmops
,
221 .probe
= dwcmshc_probe
,
222 .remove
= dwcmshc_remove
,
224 module_platform_driver(sdhci_dwcmshc_driver
);
226 MODULE_DESCRIPTION("SDHCI platform driver for Synopsys DWC MSHC");
227 MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
228 MODULE_LICENSE("GPL v2");