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 #define BOUNDARY_OK(addr, len) \
20 ((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1)))
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.
30 static void dwcmshc_adma_write_desc(struct sdhci_host
*host
, void **desc
,
31 dma_addr_t addr
, int len
, unsigned int cmd
)
35 if (likely(!len
|| BOUNDARY_OK(addr
, len
))) {
36 sdhci_adma_write_desc(host
, desc
, addr
, len
, cmd
);
40 offset
= addr
& (SZ_128M
- 1);
41 tmplen
= SZ_128M
- offset
;
42 sdhci_adma_write_desc(host
, desc
, addr
, tmplen
, cmd
);
46 sdhci_adma_write_desc(host
, desc
, addr
, len
, cmd
);
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
,
55 .adma_write_desc
= dwcmshc_adma_write_desc
,
58 static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata
= {
59 .ops
= &sdhci_dwcmshc_ops
,
60 .quirks
= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN
,
63 static int dwcmshc_probe(struct platform_device
*pdev
)
65 struct sdhci_pltfm_host
*pltfm_host
;
66 struct sdhci_host
*host
;
67 struct dwcmshc_priv
*priv
;
71 host
= sdhci_pltfm_init(pdev
, &sdhci_dwcmshc_pdata
,
72 sizeof(struct dwcmshc_priv
));
77 * extra adma table cnt for cross 128M boundary handling.
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
;
84 pltfm_host
= sdhci_priv(host
);
85 priv
= sdhci_pltfm_priv(pltfm_host
);
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
);
93 err
= clk_prepare_enable(pltfm_host
->clk
);
97 priv
->bus_clk
= devm_clk_get(&pdev
->dev
, "bus");
98 if (!IS_ERR(priv
->bus_clk
))
99 clk_prepare_enable(priv
->bus_clk
);
101 err
= mmc_of_parse(host
->mmc
);
105 sdhci_get_of_property(pdev
);
107 err
= sdhci_add_host(host
);
114 clk_disable_unprepare(pltfm_host
->clk
);
115 clk_disable_unprepare(priv
->bus_clk
);
117 sdhci_pltfm_free(pdev
);
121 static int dwcmshc_remove(struct platform_device
*pdev
)
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
);
127 sdhci_remove_host(host
, 0);
129 clk_disable_unprepare(pltfm_host
->clk
);
130 clk_disable_unprepare(priv
->bus_clk
);
132 sdhci_pltfm_free(pdev
);
137 static const struct of_device_id sdhci_dwcmshc_dt_ids
[] = {
138 { .compatible
= "snps,dwcmshc-sdhci" },
141 MODULE_DEVICE_TABLE(of
, sdhci_dwcmshc_dt_ids
);
143 static struct platform_driver sdhci_dwcmshc_driver
= {
145 .name
= "sdhci-dwcmshc",
146 .of_match_table
= sdhci_dwcmshc_dt_ids
,
148 .probe
= dwcmshc_probe
,
149 .remove
= dwcmshc_remove
,
151 module_platform_driver(sdhci_dwcmshc_driver
);
153 MODULE_DESCRIPTION("SDHCI platform driver for Synopsys DWC MSHC");
154 MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
155 MODULE_LICENSE("GPL v2");