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