aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk/tegra/clk-tegra210-emc.c
blob: 672ca8c184d2c6e9a7f26c07d036f3359af42bc4 (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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2015-2020, NVIDIA CORPORATION.  All rights reserved.
 */

#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clk/tegra.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/slab.h>

#include "clk.h"

#define CLK_SOURCE_EMC 0x19c
#define  CLK_SOURCE_EMC_2X_CLK_SRC GENMASK(31, 29)
#define  CLK_SOURCE_EMC_MC_EMC_SAME_FREQ BIT(16)
#define  CLK_SOURCE_EMC_2X_CLK_DIVISOR GENMASK(7, 0)

#define CLK_SRC_PLLM 0
#define CLK_SRC_PLLC 1
#define CLK_SRC_PLLP 2
#define CLK_SRC_CLK_M 3
#define CLK_SRC_PLLM_UD 4
#define CLK_SRC_PLLMB_UD 5
#define CLK_SRC_PLLMB 6
#define CLK_SRC_PLLP_UD 7

struct tegra210_clk_emc {
	struct clk_hw hw;
	void __iomem *regs;

	struct tegra210_clk_emc_provider *provider;

	struct clk *parents[8];
};

static inline struct tegra210_clk_emc *
to_tegra210_clk_emc(struct clk_hw *hw)
{
	return container_of(hw, struct tegra210_clk_emc, hw);
}

static const char *tegra210_clk_emc_parents[] = {
	"pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb_ud",
	"pll_mb", "pll_p_ud",
};

static u8 tegra210_clk_emc_get_parent(struct clk_hw *hw)
{
	struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
	u32 value;
	u8 src;

	value = readl_relaxed(emc->regs + CLK_SOURCE_EMC);
	src = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, value);

	return src;
}

static unsigned long tegra210_clk_emc_recalc_rate(struct clk_hw *hw,
						  unsigned long parent_rate)
{
	struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
	u32 value, div;

	/*
	 * CCF assumes that neither the parent nor its rate will change during
	 * ->set_rate(), so the parent rate passed in here was cached from the
	 * parent before the ->set_rate() call.
	 *
	 * This can lead to wrong results being reported for the EMC clock if
	 * the parent and/or parent rate have changed as part of the EMC rate
	 * change sequence. Fix this by overriding the parent clock with what
	 * we know to be the correct value after the rate change.
	 */
	parent_rate = clk_hw_get_rate(clk_hw_get_parent(hw));

	value = readl_relaxed(emc->regs + CLK_SOURCE_EMC);

	div = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_DIVISOR, value);
	div += 2;

	return DIV_ROUND_UP(parent_rate * 2, div);
}

static long tegra210_clk_emc_round_rate(struct clk_hw *hw, unsigned long rate,
					unsigned long *prate)
{
	struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
	struct tegra210_clk_emc_provider *provider = emc->provider;
	unsigned int i;

	if (!provider || !provider->configs || provider->num_configs == 0)
		return clk_hw_get_rate(hw);

	for (i = 0; i < provider->num_configs; i++) {
		if (provider->configs[i].rate >= rate)
			return provider->configs[i].rate;
	}

	return provider->configs[i - 1].rate;
}

static struct clk *tegra210_clk_emc_find_parent(struct tegra210_clk_emc *emc,
						u8 index)
{
	struct clk_hw *parent = clk_hw_get_parent_by_index(&emc->hw, index);
	const char *name = clk_hw_get_name(parent);

	/* XXX implement cache? */

	return __clk_lookup(name);
}

static int tegra210_clk_emc_set_rate(struct clk_hw *hw, unsigned long rate,
				     unsigned long parent_rate)
{
	struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
	struct tegra210_clk_emc_provider *provider = emc->provider;
	struct tegra210_clk_emc_config *config;
	struct device *dev = provider->dev;
	struct clk_hw *old, *new, *parent;
	u8 old_idx, new_idx, index;
	struct clk *clk;
	unsigned int i;
	int err;

	if (!provider->configs || provider->num_configs == 0)
		return -EINVAL;

	for (i = 0; i < provider->num_configs; i++) {
		if (provider->configs[i].rate >= rate) {
			config = &provider->configs[i];
			break;
		}
	}

	if (i == provider->num_configs)
		config = &provider->configs[i - 1];

	old_idx = tegra210_clk_emc_get_parent(hw);
	new_idx = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, config->value);

	old = clk_hw_get_parent_by_index(hw, old_idx);
	new = clk_hw_get_parent_by_index(hw, new_idx);

	/* if the rate has changed... */
	if (config->parent_rate != clk_hw_get_rate(old)) {
		/* ... but the clock source remains the same ... */
		if (new_idx == old_idx) {
			/* ... switch to the alternative clock source. */
			switch (new_idx) {
			case CLK_SRC_PLLM:
				new_idx = CLK_SRC_PLLMB;
				break;

			case CLK_SRC_PLLM_UD:
				new_idx = CLK_SRC_PLLMB_UD;
				break;

			case CLK_SRC_PLLMB_UD:
				new_idx = CLK_SRC_PLLM_UD;
				break;

			case CLK_SRC_PLLMB:
				new_idx = CLK_SRC_PLLM;
				break;
			}

			/*
			 * This should never happen because we can't deal with
			 * it.
			 */
			if (WARN_ON(new_idx == old_idx))
				return -EINVAL;

			new = clk_hw_get_parent_by_index(hw, new_idx);
		}

		index = new_idx;
		parent = new;
	} else {
		index = old_idx;
		parent = old;
	}

	clk = tegra210_clk_emc_find_parent(emc, index);
	if (IS_ERR(clk)) {
		err = PTR_ERR(clk);
		dev_err(dev, "failed to get parent clock for index %u: %d\n",
			index, err);
		return err;
	}

	/* set the new parent clock to the required rate */
	if (clk_get_rate(clk) != config->parent_rate) {
		err = clk_set_rate(clk, config->parent_rate);
		if (err < 0) {
			dev_err(dev, "failed to set rate %lu Hz for %pC: %d\n",
				config->parent_rate, clk, err);
			return err;
		}
	}

	/* enable the new parent clock */
	if (parent != old) {
		err = clk_prepare_enable(clk);
		if (err < 0) {
			dev_err(dev, "failed to enable parent clock %pC: %d\n",
				clk, err);
			return err;
		}
	}

	/* update the EMC source configuration to reflect the new parent */
	config->value &= ~CLK_SOURCE_EMC_2X_CLK_SRC;
	config->value |= FIELD_PREP(CLK_SOURCE_EMC_2X_CLK_SRC, index);

	/*
	 * Finally, switch the EMC programming with both old and new parent
	 * clocks enabled.
	 */
	err = provider->set_rate(dev, config);
	if (err < 0) {
		dev_err(dev, "failed to set EMC rate to %lu Hz: %d\n", rate,
			err);

		/*
		 * If we're unable to switch to the new EMC frequency, we no
		 * longer need the new parent to be enabled.
		 */
		if (parent != old)
			clk_disable_unprepare(clk);

		return err;
	}

	/* reparent to new parent clock and disable the old parent clock */
	if (parent != old) {
		clk = tegra210_clk_emc_find_parent(emc, old_idx);
		if (IS_ERR(clk)) {
			err = PTR_ERR(clk);
			dev_err(dev,
				"failed to get parent clock for index %u: %d\n",
				old_idx, err);
			return err;
		}

		clk_hw_reparent(hw, parent);
		clk_disable_unprepare(clk);
	}

	return err;
}

static const struct clk_ops tegra210_clk_emc_ops = {
	.get_parent = tegra210_clk_emc_get_parent,
	.recalc_rate = tegra210_clk_emc_recalc_rate,
	.round_rate = tegra210_clk_emc_round_rate,
	.set_rate = tegra210_clk_emc_set_rate,
};

struct clk *tegra210_clk_register_emc(struct device_node *np,
				      void __iomem *regs)
{
	struct tegra210_clk_emc *emc;
	struct clk_init_data init;
	struct clk *clk;

	emc = kzalloc(sizeof(*emc), GFP_KERNEL);
	if (!emc)
		return ERR_PTR(-ENOMEM);

	emc->regs = regs;

	init.name = "emc";
	init.ops = &tegra210_clk_emc_ops;
	init.flags = CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE;
	init.parent_names = tegra210_clk_emc_parents;
	init.num_parents = ARRAY_SIZE(tegra210_clk_emc_parents);
	emc->hw.init = &init;

	clk = clk_register(NULL, &emc->hw);
	if (IS_ERR(clk)) {
		kfree(emc);
		return clk;
	}

	return clk;
}

int tegra210_clk_emc_attach(struct clk *clk,
			    struct tegra210_clk_emc_provider *provider)
{
	struct clk_hw *hw = __clk_get_hw(clk);
	struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
	struct device *dev = provider->dev;
	unsigned int i;
	int err;

	if (!try_module_get(provider->owner))
		return -ENODEV;

	for (i = 0; i < provider->num_configs; i++) {
		struct tegra210_clk_emc_config *config = &provider->configs[i];
		struct clk_hw *parent;
		bool same_freq;
		u8 div, src;

		div = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_DIVISOR, config->value);
		src = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, config->value);

		/* do basic sanity checking on the EMC timings */
		if (div & 0x1) {
			dev_err(dev, "invalid odd divider %u for rate %lu Hz\n",
				div, config->rate);
			err = -EINVAL;
			goto put;
		}

		same_freq = config->value & CLK_SOURCE_EMC_MC_EMC_SAME_FREQ;

		if (same_freq != config->same_freq) {
			dev_err(dev,
				"ambiguous EMC to MC ratio for rate %lu Hz\n",
				config->rate);
			err = -EINVAL;
			goto put;
		}

		parent = clk_hw_get_parent_by_index(hw, src);
		config->parent = src;

		if (src == CLK_SRC_PLLM || src == CLK_SRC_PLLM_UD) {
			config->parent_rate = config->rate * (1 + div / 2);
		} else {
			unsigned long rate = config->rate * (1 + div / 2);

			config->parent_rate = clk_hw_get_rate(parent);

			if (config->parent_rate != rate) {
				dev_err(dev,
					"rate %lu Hz does not match input\n",
					config->rate);
				err = -EINVAL;
				goto put;
			}
		}
	}

	emc->provider = provider;

	return 0;

put:
	module_put(provider->owner);
	return err;
}
EXPORT_SYMBOL_GPL(tegra210_clk_emc_attach);

void tegra210_clk_emc_detach(struct clk *clk)
{
	struct tegra210_clk_emc *emc = to_tegra210_clk_emc(__clk_get_hw(clk));

	module_put(emc->provider->owner);
	emc->provider = NULL;
}
EXPORT_SYMBOL_GPL(tegra210_clk_emc_detach);