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