clk_rk3036.c 10.2 KB
Newer Older
huang lin's avatar
huang lin committed
1
2
3
4
5
6
7
/*
 * (C) Copyright 2015 Google, Inc
 *
 * SPDX-License-Identifier:	GPL-2.0
 */

#include <common.h>
8
#include <clk-uclass.h>
huang lin's avatar
huang lin committed
9
10
11
12
13
14
15
16
#include <dm.h>
#include <errno.h>
#include <syscon.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/arch/cru_rk3036.h>
#include <asm/arch/hardware.h>
#include <dm/lists.h>
17
#include <dt-bindings/clock/rk3036-cru.h>
18
#include <linux/log2.h>
huang lin's avatar
huang lin committed
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

DECLARE_GLOBAL_DATA_PTR;

enum {
	VCO_MAX_HZ	= 2400U * 1000000,
	VCO_MIN_HZ	= 600 * 1000000,
	OUTPUT_MAX_HZ	= 2400U * 1000000,
	OUTPUT_MIN_HZ	= 24 * 1000000,
};

#define RATE_TO_DIV(input_rate, output_rate) \
	((input_rate) / (output_rate) - 1);

#define DIV_TO_RATE(input_rate, div)	((input_rate) / ((div) + 1))

#define PLL_DIVISORS(hz, _refdiv, _postdiv1, _postdiv2) {\
	.refdiv = _refdiv,\
	.fbdiv = (u32)((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ),\
	.postdiv1 = _postdiv1, .postdiv2 = _postdiv2};\
	_Static_assert(((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ) *\
			 OSC_HZ / (_refdiv * _postdiv1 * _postdiv2) == hz,\
			 #hz "Hz cannot be hit with PLL "\
			 "divisors on line " __stringify(__LINE__));

43
/* use integer mode*/
huang lin's avatar
huang lin committed
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
static const struct pll_div apll_init_cfg = PLL_DIVISORS(APLL_HZ, 1, 3, 1);
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2, 1);

static int rkclk_set_pll(struct rk3036_cru *cru, enum rk_clk_id clk_id,
			 const struct pll_div *div)
{
	int pll_id = rk_pll_id(clk_id);
	struct rk3036_pll *pll = &cru->pll[pll_id];

	/* All PLLs have same VCO and output frequency range restrictions. */
	uint vco_hz = OSC_HZ / 1000 * div->fbdiv / div->refdiv * 1000;
	uint output_hz = vco_hz / div->postdiv1 / div->postdiv2;

	debug("PLL at %p: fbdiv=%d, refdiv=%d, postdiv1=%d, postdiv2=%d,\
		 vco=%u Hz, output=%u Hz\n",
			pll, div->fbdiv, div->refdiv, div->postdiv1,
			div->postdiv2, vco_hz, output_hz);
	assert(vco_hz >= VCO_MIN_HZ && vco_hz <= VCO_MAX_HZ &&
	       output_hz >= OUTPUT_MIN_HZ && output_hz <= OUTPUT_MAX_HZ);

64
65
	/* use integer mode */
	rk_setreg(&pll->con1, 1 << PLL_DSMPD_SHIFT);
huang lin's avatar
huang lin committed
66
67

	rk_clrsetreg(&pll->con0,
68
		     PLL_POSTDIV1_MASK | PLL_FBDIV_MASK,
huang lin's avatar
huang lin committed
69
		     (div->postdiv1 << PLL_POSTDIV1_SHIFT) | div->fbdiv);
70
71
72
	rk_clrsetreg(&pll->con1, PLL_POSTDIV2_MASK | PLL_REFDIV_MASK,
		     (div->postdiv2 << PLL_POSTDIV2_SHIFT |
		     div->refdiv << PLL_REFDIV_SHIFT));
huang lin's avatar
huang lin committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

	/* waiting for pll lock */
	while (readl(&pll->con1) & (1 << PLL_LOCK_STATUS_SHIFT))
		udelay(1);

	return 0;
}

static void rkclk_init(struct rk3036_cru *cru)
{
	u32 aclk_div;
	u32 hclk_div;
	u32 pclk_div;

	/* pll enter slow-mode */
	rk_clrsetreg(&cru->cru_mode_con,
89
		     GPLL_MODE_MASK | APLL_MODE_MASK,
huang lin's avatar
huang lin committed
90
91
92
93
94
95
96
97
		     GPLL_MODE_SLOW << GPLL_MODE_SHIFT |
		     APLL_MODE_SLOW << APLL_MODE_SHIFT);

	/* init pll */
	rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg);
	rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);

	/*
98
99
	 * select apll as cpu/core clock pll source and
	 * set up dependent divisors for PERI and ACLK clocks.
huang lin's avatar
huang lin committed
100
101
102
103
104
105
106
107
108
	 * core hz : apll = 1:1
	 */
	aclk_div = APLL_HZ / CORE_ACLK_HZ - 1;
	assert((aclk_div + 1) * CORE_ACLK_HZ == APLL_HZ && aclk_div < 0x7);

	pclk_div = APLL_HZ / CORE_PERI_HZ - 1;
	assert((pclk_div + 1) * CORE_PERI_HZ == APLL_HZ && pclk_div < 0xf);

	rk_clrsetreg(&cru->cru_clksel_con[0],
109
		     CORE_CLK_PLL_SEL_MASK | CORE_DIV_CON_MASK,
huang lin's avatar
huang lin committed
110
111
112
113
		     CORE_CLK_PLL_SEL_APLL << CORE_CLK_PLL_SEL_SHIFT |
		     0 << CORE_DIV_CON_SHIFT);

	rk_clrsetreg(&cru->cru_clksel_con[1],
114
		     CORE_ACLK_DIV_MASK | CORE_PERI_DIV_MASK,
huang lin's avatar
huang lin committed
115
116
117
118
		     aclk_div << CORE_ACLK_DIV_SHIFT |
		     pclk_div << CORE_PERI_DIV_SHIFT);

	/*
119
	 * select apll as pd_bus bus clock source and
huang lin's avatar
huang lin committed
120
121
	 * set up dependent divisors for PCLK/HCLK and ACLK clocks.
	 */
122
123
	aclk_div = GPLL_HZ / BUS_ACLK_HZ - 1;
	assert((aclk_div + 1) * BUS_ACLK_HZ == GPLL_HZ && aclk_div <= 0x1f);
huang lin's avatar
huang lin committed
124

125
126
	pclk_div = GPLL_HZ / BUS_PCLK_HZ - 1;
	assert((pclk_div + 1) * BUS_PCLK_HZ == GPLL_HZ && pclk_div <= 0x7);
huang lin's avatar
huang lin committed
127

128
129
	hclk_div = GPLL_HZ / BUS_HCLK_HZ - 1;
	assert((hclk_div + 1) * BUS_HCLK_HZ == GPLL_HZ && hclk_div <= 0x3);
huang lin's avatar
huang lin committed
130
131

	rk_clrsetreg(&cru->cru_clksel_con[0],
132
		     BUS_ACLK_PLL_SEL_MASK | BUS_ACLK_DIV_MASK,
133
		     BUS_ACLK_PLL_SEL_GPLL << BUS_ACLK_PLL_SEL_SHIFT |
134
		     aclk_div << BUS_ACLK_DIV_SHIFT);
huang lin's avatar
huang lin committed
135
136

	rk_clrsetreg(&cru->cru_clksel_con[1],
137
138
139
		     BUS_PCLK_DIV_MASK | BUS_HCLK_DIV_MASK,
		     pclk_div << BUS_PCLK_DIV_SHIFT |
		     hclk_div << BUS_HCLK_DIV_SHIFT);
huang lin's avatar
huang lin committed
140
141

	/*
142
	 * select gpll as pd_peri bus clock source and
huang lin's avatar
huang lin committed
143
144
145
146
147
	 * set up dependent divisors for PCLK/HCLK and ACLK clocks.
	 */
	aclk_div = GPLL_HZ / PERI_ACLK_HZ - 1;
	assert((aclk_div + 1) * PERI_ACLK_HZ == GPLL_HZ && aclk_div < 0x1f);

148
	hclk_div = ilog2(PERI_ACLK_HZ / PERI_HCLK_HZ);
huang lin's avatar
huang lin committed
149
	assert((1 << hclk_div) * PERI_HCLK_HZ ==
150
		PERI_ACLK_HZ && (hclk_div < 0x4));
huang lin's avatar
huang lin committed
151

152
	pclk_div = ilog2(PERI_ACLK_HZ / PERI_PCLK_HZ);
huang lin's avatar
huang lin committed
153
154
155
156
	assert((1 << pclk_div) * PERI_PCLK_HZ ==
		PERI_ACLK_HZ && pclk_div < 0x8);

	rk_clrsetreg(&cru->cru_clksel_con[10],
157
158
		     PERI_PLL_SEL_MASK | PERI_PCLK_DIV_MASK |
		     PERI_HCLK_DIV_MASK | PERI_ACLK_DIV_MASK,
huang lin's avatar
huang lin committed
159
160
161
162
163
164
165
		     PERI_PLL_GPLL << PERI_PLL_SEL_SHIFT |
		     pclk_div << PERI_PCLK_DIV_SHIFT |
		     hclk_div << PERI_HCLK_DIV_SHIFT |
		     aclk_div << PERI_ACLK_DIV_SHIFT);

	/* PLL enter normal-mode */
	rk_clrsetreg(&cru->cru_mode_con,
166
		     GPLL_MODE_MASK | APLL_MODE_MASK,
huang lin's avatar
huang lin committed
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
		     GPLL_MODE_NORM << GPLL_MODE_SHIFT |
		     APLL_MODE_NORM << APLL_MODE_SHIFT);
}

/* Get pll rate by id */
static uint32_t rkclk_pll_get_rate(struct rk3036_cru *cru,
				   enum rk_clk_id clk_id)
{
	uint32_t refdiv, fbdiv, postdiv1, postdiv2;
	uint32_t con;
	int pll_id = rk_pll_id(clk_id);
	struct rk3036_pll *pll = &cru->pll[pll_id];
	static u8 clk_shift[CLK_COUNT] = {
		0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, 0xff,
		GPLL_MODE_SHIFT, 0xff
	};
183
184
185
	static u32 clk_mask[CLK_COUNT] = {
		0xffffffff, APLL_MODE_MASK, DPLL_MODE_MASK, 0xffffffff,
		GPLL_MODE_MASK, 0xffffffff
huang lin's avatar
huang lin committed
186
187
188
189
190
191
192
193
	};
	uint shift;
	uint mask;

	con = readl(&cru->cru_mode_con);
	shift = clk_shift[clk_id];
	mask = clk_mask[clk_id];

194
	switch ((con & mask) >> shift) {
huang lin's avatar
huang lin committed
195
196
197
198
199
200
	case GPLL_MODE_SLOW:
		return OSC_HZ;
	case GPLL_MODE_NORM:

		/* normal mode */
		con = readl(&pll->con0);
201
202
		postdiv1 = (con & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT;
		fbdiv = (con & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT;
huang lin's avatar
huang lin committed
203
		con = readl(&pll->con1);
204
205
		postdiv2 = (con & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT;
		refdiv = (con & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT;
huang lin's avatar
huang lin committed
206
207
208
209
210
211
212
213
		return (24 * fbdiv / (refdiv * postdiv1 * postdiv2)) * 1000000;
	case GPLL_MODE_DEEP:
	default:
		return 32768;
	}
}

static ulong rockchip_mmc_get_clk(struct rk3036_cru *cru, uint clk_general_rate,
214
				  int periph)
huang lin's avatar
huang lin committed
215
216
217
218
219
220
{
	uint src_rate;
	uint div, mux;
	u32 con;

	switch (periph) {
221
	case HCLK_EMMC:
222
	case SCLK_EMMC:
huang lin's avatar
huang lin committed
223
		con = readl(&cru->cru_clksel_con[12]);
224
225
		mux = (con & EMMC_PLL_MASK) >> EMMC_PLL_SHIFT;
		div = (con & EMMC_DIV_MASK) >> EMMC_DIV_SHIFT;
huang lin's avatar
huang lin committed
226
		break;
227
	case HCLK_SDIO:
228
	case SCLK_SDIO:
huang lin's avatar
huang lin committed
229
		con = readl(&cru->cru_clksel_con[12]);
230
231
		mux = (con & MMC0_PLL_MASK) >> MMC0_PLL_SHIFT;
		div = (con & MMC0_DIV_MASK) >> MMC0_DIV_SHIFT;
huang lin's avatar
huang lin committed
232
233
234
235
236
237
		break;
	default:
		return -EINVAL;
	}

	src_rate = mux == EMMC_SEL_24M ? OSC_HZ : clk_general_rate;
238
	return DIV_TO_RATE(src_rate, div) / 2;
huang lin's avatar
huang lin committed
239
240
241
}

static ulong rockchip_mmc_set_clk(struct rk3036_cru *cru, uint clk_general_rate,
242
				  int periph, uint freq)
huang lin's avatar
huang lin committed
243
244
245
246
247
248
249
{
	int src_clk_div;
	int mux;

	debug("%s: clk_general_rate=%u\n", __func__, clk_general_rate);

	/* mmc clock auto divide 2 in internal */
250
	src_clk_div = DIV_ROUND_UP(clk_general_rate / 2, freq);
huang lin's avatar
huang lin committed
251

Kever Yang's avatar
Kever Yang committed
252
	if (src_clk_div > 128) {
253
		src_clk_div = DIV_ROUND_UP(OSC_HZ / 2, freq);
Kever Yang's avatar
Kever Yang committed
254
		assert(src_clk_div - 1 < 128);
huang lin's avatar
huang lin committed
255
256
257
258
259
260
		mux = EMMC_SEL_24M;
	} else {
		mux = EMMC_SEL_GPLL;
	}

	switch (periph) {
261
	case HCLK_EMMC:
262
	case SCLK_EMMC:
huang lin's avatar
huang lin committed
263
		rk_clrsetreg(&cru->cru_clksel_con[12],
264
			     EMMC_PLL_MASK | EMMC_DIV_MASK,
huang lin's avatar
huang lin committed
265
266
267
			     mux << EMMC_PLL_SHIFT |
			     (src_clk_div - 1) << EMMC_DIV_SHIFT);
		break;
268
	case HCLK_SDIO:
269
	case SCLK_SDIO:
huang lin's avatar
huang lin committed
270
		rk_clrsetreg(&cru->cru_clksel_con[11],
271
			     MMC0_PLL_MASK | MMC0_DIV_MASK,
huang lin's avatar
huang lin committed
272
273
274
275
276
277
278
279
280
281
			     mux << MMC0_PLL_SHIFT |
			     (src_clk_div - 1) << MMC0_DIV_SHIFT);
		break;
	default:
		return -EINVAL;
	}

	return rockchip_mmc_get_clk(cru, clk_general_rate, periph);
}

282
static ulong rk3036_clk_get_rate(struct clk *clk)
huang lin's avatar
huang lin committed
283
{
284
	struct rk3036_clk_priv *priv = dev_get_priv(clk->dev);
huang lin's avatar
huang lin committed
285

286
287
288
289
290
291
	switch (clk->id) {
	case 0 ... 63:
		return rkclk_pll_get_rate(priv->cru, clk->id);
	default:
		return -ENOENT;
	}
huang lin's avatar
huang lin committed
292
293
}

294
static ulong rk3036_clk_set_rate(struct clk *clk, ulong rate)
huang lin's avatar
huang lin committed
295
{
296
297
	struct rk3036_clk_priv *priv = dev_get_priv(clk->dev);
	ulong new_rate, gclk_rate;
huang lin's avatar
huang lin committed
298

299
300
301
302
	gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL);
	switch (clk->id) {
	case 0 ... 63:
		return 0;
303
	case HCLK_EMMC:
304
	case SCLK_EMMC:
305
306
		new_rate = rockchip_mmc_set_clk(priv->cru, gclk_rate,
						clk->id, rate);
huang lin's avatar
huang lin committed
307
308
309
310
311
312
313
314
315
316
317
318
319
		break;
	default:
		return -ENOENT;
	}

	return new_rate;
}

static struct clk_ops rk3036_clk_ops = {
	.get_rate	= rk3036_clk_get_rate,
	.set_rate	= rk3036_clk_set_rate,
};

320
static int rk3036_clk_ofdata_to_platdata(struct udevice *dev)
huang lin's avatar
huang lin committed
321
322
323
{
	struct rk3036_clk_priv *priv = dev_get_priv(dev);

324
	priv->cru = dev_read_addr_ptr(dev);
325
326
327
328
329
330
331
332

	return 0;
}

static int rk3036_clk_probe(struct udevice *dev)
{
	struct rk3036_clk_priv *priv = dev_get_priv(dev);

huang lin's avatar
huang lin committed
333
334
335
336
337
338
339
	rkclk_init(priv->cru);

	return 0;
}

static int rk3036_clk_bind(struct udevice *dev)
{
340
	int ret;
341
342
	struct udevice *sys_child;
	struct sysreset_reg *priv;
huang lin's avatar
huang lin committed
343
344

	/* The reset driver does not have a device node, so bind it here */
345
346
347
348
349
350
351
352
353
354
355
356
	ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
				 &sys_child);
	if (ret) {
		debug("Warning: No sysreset driver: ret=%d\n", ret);
	} else {
		priv = malloc(sizeof(struct sysreset_reg));
		priv->glb_srst_fst_value = offsetof(struct rk3036_cru,
						    cru_glb_srst_fst_value);
		priv->glb_srst_snd_value = offsetof(struct rk3036_cru,
						    cru_glb_srst_snd_value);
		sys_child->priv = priv;
	}
huang lin's avatar
huang lin committed
357

358
359
360
361
362
363
364
#if CONFIG_IS_ENABLED(CONFIG_RESET_ROCKCHIP)
	ret = offsetof(struct rk3036_cru, cru_softrst_con[0]);
	ret = rockchip_reset_bind(dev, ret, 9);
	if (ret)
		debug("Warning: software reset driver bind faile\n");
#endif

huang lin's avatar
huang lin committed
365
366
367
368
369
370
371
372
	return 0;
}

static const struct udevice_id rk3036_clk_ids[] = {
	{ .compatible = "rockchip,rk3036-cru" },
	{ }
};

373
U_BOOT_DRIVER(rockchip_rk3036_cru) = {
huang lin's avatar
huang lin committed
374
375
376
377
	.name		= "clk_rk3036",
	.id		= UCLASS_CLK,
	.of_match	= rk3036_clk_ids,
	.priv_auto_alloc_size = sizeof(struct rk3036_clk_priv),
378
	.ofdata_to_platdata = rk3036_clk_ofdata_to_platdata,
huang lin's avatar
huang lin committed
379
380
381
382
	.ops		= &rk3036_clk_ops,
	.bind		= rk3036_clk_bind,
	.probe		= rk3036_clk_probe,
};