summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/dw_mmc-hi3798mv200.c
blob: 96af693e3e37ce64e8a4dc443306f5717beceda7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Modified from dw_mmc-hi3798cv200.c
 *
 * Copyright (c) 2024 Yang Xiwen <forbidden405@outlook.com>
 * Copyright (c) 2018 HiSilicon Technologies Co., Ltd.
 */

#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/mmc/host.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>

#include "dw_mmc.h"
#include "dw_mmc-pltfm.h"

#define SDMMC_TUNING_CTRL	0x118
#define SDMMC_TUNING_FIND_EDGE	BIT(5)

#define ALL_INT_CLR		0x1ffff

/* DLL ctrl reg */
#define SAP_DLL_CTRL_DLLMODE	BIT(16)

struct dw_mci_hi3798mv200_priv {
	struct clk *sample_clk;
	struct clk *drive_clk;
	struct regmap *crg_reg;
	u32 sap_dll_offset;
	struct mmc_clk_phase_map phase_map;
};

static void dw_mci_hi3798mv200_set_ios(struct dw_mci *host, struct mmc_ios *ios)
{
	struct dw_mci_hi3798mv200_priv *priv = host->priv;
	struct mmc_clk_phase phase = priv->phase_map.phase[ios->timing];
	u32 val;

	val = mci_readl(host, ENABLE_SHIFT);
	if (ios->timing == MMC_TIMING_MMC_DDR52
	    || ios->timing == MMC_TIMING_UHS_DDR50)
		val |= SDMMC_ENABLE_PHASE;
	else
		val &= ~SDMMC_ENABLE_PHASE;
	mci_writel(host, ENABLE_SHIFT, val);

	val = mci_readl(host, DDR_REG);
	if (ios->timing == MMC_TIMING_MMC_HS400)
		val |= SDMMC_DDR_HS400;
	else
		val &= ~SDMMC_DDR_HS400;
	mci_writel(host, DDR_REG, val);

	if (clk_set_rate(host->ciu_clk, ios->clock))
		dev_warn(host->dev, "Failed to set rate to %u\n", ios->clock);
	else
		/*
		 * CLK_MUX_ROUND_NEAREST is enabled for this clock
		 * The actual clock rate is not what we set, but a rounded value
		 * so we should get the rate once again
		 */
		host->bus_hz = clk_get_rate(host->ciu_clk);

	if (phase.valid) {
		clk_set_phase(priv->drive_clk, phase.out_deg);
		clk_set_phase(priv->sample_clk, phase.in_deg);
	} else {
		dev_warn(host->dev,
			 "The phase entry for timing mode %d is missing in device tree.\n",
			 ios->timing);
	}
}

static inline int dw_mci_hi3798mv200_enable_tuning(struct dw_mci_slot *slot)
{
	struct dw_mci_hi3798mv200_priv *priv = slot->host->priv;

	return regmap_clear_bits(priv->crg_reg, priv->sap_dll_offset, SAP_DLL_CTRL_DLLMODE);
}

static inline int dw_mci_hi3798mv200_disable_tuning(struct dw_mci_slot *slot)
{
	struct dw_mci_hi3798mv200_priv *priv = slot->host->priv;

	return regmap_set_bits(priv->crg_reg, priv->sap_dll_offset, SAP_DLL_CTRL_DLLMODE);
}

static int dw_mci_hi3798mv200_execute_tuning_mix_mode(struct dw_mci_slot *slot,
					     u32 opcode)
{
	static const int degrees[] = { 0, 45, 90, 135, 180, 225, 270, 315 };
	struct dw_mci *host = slot->host;
	struct dw_mci_hi3798mv200_priv *priv = host->priv;
	int raise_point = -1, fall_point = -1, mid;
	int err, prev_err = -1;
	int found = 0;
	int regval;
	int i;
	int ret;

	ret = dw_mci_hi3798mv200_enable_tuning(slot);
	if (ret < 0)
		return ret;

	for (i = 0; i < ARRAY_SIZE(degrees); i++) {
		clk_set_phase(priv->sample_clk, degrees[i]);
		mci_writel(host, RINTSTS, ALL_INT_CLR);

		/*
		 * HiSilicon implemented a tuning mechanism.
		 * It needs special interaction with the DLL.
		 *
		 * Treat edge(flip) found as an error too.
		 */
		err = mmc_send_tuning(slot->mmc, opcode, NULL);
		regval = mci_readl(host, TUNING_CTRL);
		if (err || (regval & SDMMC_TUNING_FIND_EDGE))
			err = 1;
		else
			found = 1;

		if (i > 0) {
			if (err && !prev_err)
				fall_point = i - 1;
			if (!err && prev_err)
				raise_point = i;
		}

		if (raise_point != -1 && fall_point != -1)
			goto tuning_out;

		prev_err = err;
	}

tuning_out:
	ret = dw_mci_hi3798mv200_disable_tuning(slot);
	if (ret < 0)
		return ret;

	if (found) {
		if (raise_point == -1)
			raise_point = 0;
		if (fall_point == -1)
			fall_point = ARRAY_SIZE(degrees) - 1;
		if (fall_point < raise_point) {
			if ((raise_point + fall_point) >
			    (ARRAY_SIZE(degrees) - 1))
				mid = fall_point / 2;
			else
				mid = (raise_point + ARRAY_SIZE(degrees) - 1) / 2;
		} else {
			mid = (raise_point + fall_point) / 2;
		}

		/*
		 * We don't care what timing we are tuning for,
		 * simply use the same phase for all timing needs tuning.
		 */
		priv->phase_map.phase[MMC_TIMING_MMC_HS200].in_deg = degrees[mid];
		priv->phase_map.phase[MMC_TIMING_MMC_HS400].in_deg = degrees[mid];
		priv->phase_map.phase[MMC_TIMING_UHS_SDR104].in_deg = degrees[mid];

		clk_set_phase(priv->sample_clk, degrees[mid]);
		dev_dbg(host->dev, "Tuning clk_sample[%d, %d], set[%d]\n",
			raise_point, fall_point, degrees[mid]);
		ret = 0;
	} else {
		dev_err(host->dev, "No valid clk_sample shift!\n");
		ret = -EINVAL;
	}

	mci_writel(host, RINTSTS, ALL_INT_CLR);

	return ret;
}

static int dw_mci_hi3798mv200_init(struct dw_mci *host)
{
	struct dw_mci_hi3798mv200_priv *priv;
	struct device_node *np = host->dev->of_node;
	int ret;

	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	mmc_of_parse_clk_phase(host->dev, &priv->phase_map);

	priv->sample_clk = devm_clk_get_enabled(host->dev, "ciu-sample");
	if (IS_ERR(priv->sample_clk))
		return dev_err_probe(host->dev, PTR_ERR(priv->sample_clk),
				     "failed to get enabled ciu-sample clock\n");

	priv->drive_clk = devm_clk_get_enabled(host->dev, "ciu-drive");
	if (IS_ERR(priv->drive_clk))
		return dev_err_probe(host->dev, PTR_ERR(priv->drive_clk),
				     "failed to get enabled ciu-drive clock\n");

	priv->crg_reg = syscon_regmap_lookup_by_phandle(np, "hisilicon,sap-dll-reg");
	if (IS_ERR(priv->crg_reg))
		return dev_err_probe(host->dev, PTR_ERR(priv->crg_reg),
				     "failed to get CRG reg\n");

	ret = of_property_read_u32_index(np, "hisilicon,sap-dll-reg", 1, &priv->sap_dll_offset);
	if (ret)
		return dev_err_probe(host->dev, ret, "failed to get sample DLL register offset\n");

	host->priv = priv;
	return 0;
}

static const struct dw_mci_drv_data hi3798mv200_data = {
	.common_caps = MMC_CAP_CMD23,
	.init = dw_mci_hi3798mv200_init,
	.set_ios = dw_mci_hi3798mv200_set_ios,
	.execute_tuning = dw_mci_hi3798mv200_execute_tuning_mix_mode,
};

static const struct of_device_id dw_mci_hi3798mv200_match[] = {
	{ .compatible = "hisilicon,hi3798mv200-dw-mshc" },
	{},
};

static int dw_mci_hi3798mv200_probe(struct platform_device *pdev)
{
	return dw_mci_pltfm_register(pdev, &hi3798mv200_data);
}

static void dw_mci_hi3798mv200_remove(struct platform_device *pdev)
{
	dw_mci_pltfm_remove(pdev);
}

MODULE_DEVICE_TABLE(of, dw_mci_hi3798mv200_match);
static struct platform_driver dw_mci_hi3798mv200_driver = {
	.probe = dw_mci_hi3798mv200_probe,
	.remove_new = dw_mci_hi3798mv200_remove,
	.driver = {
		.name = "dwmmc_hi3798mv200",
		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
		.of_match_table = dw_mci_hi3798mv200_match,
	},
};
module_platform_driver(dw_mci_hi3798mv200_driver);

MODULE_DESCRIPTION("HiSilicon Hi3798MV200 Specific DW-MSHC Driver Extension");
MODULE_LICENSE("GPL");