xref: /linux/drivers/mmc/host/dw_mmc-hi3798cv200.c (revision a1a489197a07ec1dbf3f6033b84755a74efc65dc)
1e382ab74Stianshuliang // SPDX-License-Identifier: GPL-2.0
2e382ab74Stianshuliang /*
3e382ab74Stianshuliang  * Copyright (c) 2018 HiSilicon Technologies Co., Ltd.
4e382ab74Stianshuliang  */
5e382ab74Stianshuliang 
6e382ab74Stianshuliang #include <linux/clk.h>
7e382ab74Stianshuliang #include <linux/mfd/syscon.h>
8e382ab74Stianshuliang #include <linux/mmc/host.h>
9e382ab74Stianshuliang #include <linux/module.h>
10e382ab74Stianshuliang #include <linux/of_address.h>
11e382ab74Stianshuliang #include <linux/platform_device.h>
12e382ab74Stianshuliang #include <linux/pm_runtime.h>
13e382ab74Stianshuliang #include <linux/regmap.h>
14e382ab74Stianshuliang #include <linux/regulator/consumer.h>
15e382ab74Stianshuliang 
16e382ab74Stianshuliang #include "dw_mmc.h"
17e382ab74Stianshuliang #include "dw_mmc-pltfm.h"
18e382ab74Stianshuliang 
19e382ab74Stianshuliang #define ALL_INT_CLR		0x1ffff
20e382ab74Stianshuliang 
21e382ab74Stianshuliang struct hi3798cv200_priv {
22e382ab74Stianshuliang 	struct clk *sample_clk;
23e382ab74Stianshuliang 	struct clk *drive_clk;
24e382ab74Stianshuliang };
25e382ab74Stianshuliang 
26ed3ae724SIgor Opaniuk static unsigned long dw_mci_hi3798cv200_caps[] = {
27ed3ae724SIgor Opaniuk 	MMC_CAP_CMD23,
28ed3ae724SIgor Opaniuk 	MMC_CAP_CMD23,
29ed3ae724SIgor Opaniuk 	MMC_CAP_CMD23
30ed3ae724SIgor Opaniuk };
31ed3ae724SIgor Opaniuk 
32e382ab74Stianshuliang static void dw_mci_hi3798cv200_set_ios(struct dw_mci *host, struct mmc_ios *ios)
33e382ab74Stianshuliang {
34e382ab74Stianshuliang 	struct hi3798cv200_priv *priv = host->priv;
35e382ab74Stianshuliang 	u32 val;
36e382ab74Stianshuliang 
37e382ab74Stianshuliang 	val = mci_readl(host, UHS_REG);
38e382ab74Stianshuliang 	if (ios->timing == MMC_TIMING_MMC_DDR52 ||
39e382ab74Stianshuliang 	    ios->timing == MMC_TIMING_UHS_DDR50)
40e382ab74Stianshuliang 		val |= SDMMC_UHS_DDR;
41e382ab74Stianshuliang 	else
42e382ab74Stianshuliang 		val &= ~SDMMC_UHS_DDR;
43e382ab74Stianshuliang 	mci_writel(host, UHS_REG, val);
44e382ab74Stianshuliang 
45e382ab74Stianshuliang 	val = mci_readl(host, ENABLE_SHIFT);
46e382ab74Stianshuliang 	if (ios->timing == MMC_TIMING_MMC_DDR52)
47e382ab74Stianshuliang 		val |= SDMMC_ENABLE_PHASE;
48e382ab74Stianshuliang 	else
49e382ab74Stianshuliang 		val &= ~SDMMC_ENABLE_PHASE;
50e382ab74Stianshuliang 	mci_writel(host, ENABLE_SHIFT, val);
51e382ab74Stianshuliang 
52e382ab74Stianshuliang 	val = mci_readl(host, DDR_REG);
53e382ab74Stianshuliang 	if (ios->timing == MMC_TIMING_MMC_HS400)
54e382ab74Stianshuliang 		val |= SDMMC_DDR_HS400;
55e382ab74Stianshuliang 	else
56e382ab74Stianshuliang 		val &= ~SDMMC_DDR_HS400;
57e382ab74Stianshuliang 	mci_writel(host, DDR_REG, val);
58e382ab74Stianshuliang 
59e382ab74Stianshuliang 	if (ios->timing == MMC_TIMING_MMC_HS ||
60e382ab74Stianshuliang 	    ios->timing == MMC_TIMING_LEGACY)
61e382ab74Stianshuliang 		clk_set_phase(priv->drive_clk, 180);
62e382ab74Stianshuliang 	else if (ios->timing == MMC_TIMING_MMC_HS200)
63e382ab74Stianshuliang 		clk_set_phase(priv->drive_clk, 135);
64e382ab74Stianshuliang }
65e382ab74Stianshuliang 
66e382ab74Stianshuliang static int dw_mci_hi3798cv200_execute_tuning(struct dw_mci_slot *slot,
67e382ab74Stianshuliang 					     u32 opcode)
68e382ab74Stianshuliang {
693fb2009aSColin Ian King 	static const int degrees[] = { 0, 45, 90, 135, 180, 225, 270, 315 };
70e382ab74Stianshuliang 	struct dw_mci *host = slot->host;
71e382ab74Stianshuliang 	struct hi3798cv200_priv *priv = host->priv;
72e382ab74Stianshuliang 	int raise_point = -1, fall_point = -1;
73e382ab74Stianshuliang 	int err, prev_err = -1;
74e382ab74Stianshuliang 	int found = 0;
75e382ab74Stianshuliang 	int i;
76e382ab74Stianshuliang 
77e382ab74Stianshuliang 	for (i = 0; i < ARRAY_SIZE(degrees); i++) {
78e382ab74Stianshuliang 		clk_set_phase(priv->sample_clk, degrees[i]);
79e382ab74Stianshuliang 		mci_writel(host, RINTSTS, ALL_INT_CLR);
80e382ab74Stianshuliang 
81e382ab74Stianshuliang 		err = mmc_send_tuning(slot->mmc, opcode, NULL);
82e382ab74Stianshuliang 		if (!err)
83e382ab74Stianshuliang 			found = 1;
84e382ab74Stianshuliang 
85e382ab74Stianshuliang 		if (i > 0) {
86e382ab74Stianshuliang 			if (err && !prev_err)
87e382ab74Stianshuliang 				fall_point = i - 1;
88e382ab74Stianshuliang 			if (!err && prev_err)
89e382ab74Stianshuliang 				raise_point = i;
90e382ab74Stianshuliang 		}
91e382ab74Stianshuliang 
92e382ab74Stianshuliang 		if (raise_point != -1 && fall_point != -1)
93e382ab74Stianshuliang 			goto tuning_out;
94e382ab74Stianshuliang 
95e382ab74Stianshuliang 		prev_err = err;
96e382ab74Stianshuliang 		err = 0;
97e382ab74Stianshuliang 	}
98e382ab74Stianshuliang 
99e382ab74Stianshuliang tuning_out:
100e382ab74Stianshuliang 	if (found) {
101e382ab74Stianshuliang 		if (raise_point == -1)
102e382ab74Stianshuliang 			raise_point = 0;
103e382ab74Stianshuliang 		if (fall_point == -1)
104e382ab74Stianshuliang 			fall_point = ARRAY_SIZE(degrees) - 1;
105e382ab74Stianshuliang 		if (fall_point < raise_point) {
106e382ab74Stianshuliang 			if ((raise_point + fall_point) >
107e382ab74Stianshuliang 			    (ARRAY_SIZE(degrees) - 1))
108e382ab74Stianshuliang 				i = fall_point / 2;
109e382ab74Stianshuliang 			else
110e382ab74Stianshuliang 				i = (raise_point + ARRAY_SIZE(degrees) - 1) / 2;
111e382ab74Stianshuliang 		} else {
112e382ab74Stianshuliang 			i = (raise_point + fall_point) / 2;
113e382ab74Stianshuliang 		}
114e382ab74Stianshuliang 
115e382ab74Stianshuliang 		clk_set_phase(priv->sample_clk, degrees[i]);
116e382ab74Stianshuliang 		dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n",
117e382ab74Stianshuliang 			raise_point, fall_point, degrees[i]);
118e382ab74Stianshuliang 	} else {
119e382ab74Stianshuliang 		dev_err(host->dev, "No valid clk_sample shift! use default\n");
120e382ab74Stianshuliang 		err = -EINVAL;
121e382ab74Stianshuliang 	}
122e382ab74Stianshuliang 
123e382ab74Stianshuliang 	mci_writel(host, RINTSTS, ALL_INT_CLR);
124e382ab74Stianshuliang 	return err;
125e382ab74Stianshuliang }
126e382ab74Stianshuliang 
127e382ab74Stianshuliang static int dw_mci_hi3798cv200_init(struct dw_mci *host)
128e382ab74Stianshuliang {
129e382ab74Stianshuliang 	struct hi3798cv200_priv *priv;
130e382ab74Stianshuliang 	int ret;
131e382ab74Stianshuliang 
132e382ab74Stianshuliang 	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
133e382ab74Stianshuliang 	if (!priv)
134e382ab74Stianshuliang 		return -ENOMEM;
135e382ab74Stianshuliang 
136e382ab74Stianshuliang 	priv->sample_clk = devm_clk_get(host->dev, "ciu-sample");
137e382ab74Stianshuliang 	if (IS_ERR(priv->sample_clk)) {
138e382ab74Stianshuliang 		dev_err(host->dev, "failed to get ciu-sample clock\n");
139e382ab74Stianshuliang 		return PTR_ERR(priv->sample_clk);
140e382ab74Stianshuliang 	}
141e382ab74Stianshuliang 
142e382ab74Stianshuliang 	priv->drive_clk = devm_clk_get(host->dev, "ciu-drive");
143e382ab74Stianshuliang 	if (IS_ERR(priv->drive_clk)) {
144e382ab74Stianshuliang 		dev_err(host->dev, "failed to get ciu-drive clock\n");
145e382ab74Stianshuliang 		return PTR_ERR(priv->drive_clk);
146e382ab74Stianshuliang 	}
147e382ab74Stianshuliang 
148e382ab74Stianshuliang 	ret = clk_prepare_enable(priv->sample_clk);
149e382ab74Stianshuliang 	if (ret) {
150e382ab74Stianshuliang 		dev_err(host->dev, "failed to enable ciu-sample clock\n");
151e382ab74Stianshuliang 		return ret;
152e382ab74Stianshuliang 	}
153e382ab74Stianshuliang 
154e382ab74Stianshuliang 	ret = clk_prepare_enable(priv->drive_clk);
155e382ab74Stianshuliang 	if (ret) {
156e382ab74Stianshuliang 		dev_err(host->dev, "failed to enable ciu-drive clock\n");
157e382ab74Stianshuliang 		goto disable_sample_clk;
158e382ab74Stianshuliang 	}
159e382ab74Stianshuliang 
160e382ab74Stianshuliang 	host->priv = priv;
161e382ab74Stianshuliang 	return 0;
162e382ab74Stianshuliang 
163e382ab74Stianshuliang disable_sample_clk:
164e382ab74Stianshuliang 	clk_disable_unprepare(priv->sample_clk);
165e382ab74Stianshuliang 	return ret;
166e382ab74Stianshuliang }
167e382ab74Stianshuliang 
168e382ab74Stianshuliang static const struct dw_mci_drv_data hi3798cv200_data = {
169ed3ae724SIgor Opaniuk 	.caps = dw_mci_hi3798cv200_caps,
170ed3ae724SIgor Opaniuk 	.num_caps = ARRAY_SIZE(dw_mci_hi3798cv200_caps),
171e382ab74Stianshuliang 	.init = dw_mci_hi3798cv200_init,
172e382ab74Stianshuliang 	.set_ios = dw_mci_hi3798cv200_set_ios,
173e382ab74Stianshuliang 	.execute_tuning = dw_mci_hi3798cv200_execute_tuning,
174e382ab74Stianshuliang };
175e382ab74Stianshuliang 
176e382ab74Stianshuliang static int dw_mci_hi3798cv200_probe(struct platform_device *pdev)
177e382ab74Stianshuliang {
178e382ab74Stianshuliang 	return dw_mci_pltfm_register(pdev, &hi3798cv200_data);
179e382ab74Stianshuliang }
180e382ab74Stianshuliang 
181e382ab74Stianshuliang static int dw_mci_hi3798cv200_remove(struct platform_device *pdev)
182e382ab74Stianshuliang {
183e382ab74Stianshuliang 	struct dw_mci *host = platform_get_drvdata(pdev);
184e382ab74Stianshuliang 	struct hi3798cv200_priv *priv = host->priv;
185e382ab74Stianshuliang 
186e382ab74Stianshuliang 	clk_disable_unprepare(priv->drive_clk);
187e382ab74Stianshuliang 	clk_disable_unprepare(priv->sample_clk);
188e382ab74Stianshuliang 
189e382ab74Stianshuliang 	return dw_mci_pltfm_remove(pdev);
190e382ab74Stianshuliang }
191e382ab74Stianshuliang 
192e382ab74Stianshuliang static const struct of_device_id dw_mci_hi3798cv200_match[] = {
193e382ab74Stianshuliang 	{ .compatible = "hisilicon,hi3798cv200-dw-mshc", },
194e382ab74Stianshuliang 	{},
195e382ab74Stianshuliang };
196e382ab74Stianshuliang 
197e382ab74Stianshuliang MODULE_DEVICE_TABLE(of, dw_mci_hi3798cv200_match);
198e382ab74Stianshuliang static struct platform_driver dw_mci_hi3798cv200_driver = {
199e382ab74Stianshuliang 	.probe = dw_mci_hi3798cv200_probe,
200e382ab74Stianshuliang 	.remove = dw_mci_hi3798cv200_remove,
201e382ab74Stianshuliang 	.driver = {
202e382ab74Stianshuliang 		.name = "dwmmc_hi3798cv200",
203*a1a48919SDouglas Anderson 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
204e382ab74Stianshuliang 		.of_match_table = dw_mci_hi3798cv200_match,
205e382ab74Stianshuliang 	},
206e382ab74Stianshuliang };
207e382ab74Stianshuliang module_platform_driver(dw_mci_hi3798cv200_driver);
208e382ab74Stianshuliang 
209e382ab74Stianshuliang MODULE_DESCRIPTION("HiSilicon Hi3798CV200 Specific DW-MSHC Driver Extension");
210e382ab74Stianshuliang MODULE_LICENSE("GPL v2");
211e382ab74Stianshuliang MODULE_ALIAS("platform:dwmmc_hi3798cv200");
212