xref: /rk3399_rockchip-uboot/drivers/clk/rockchip/clk_rk3036.c (revision df77e7a38cff3076ccfec5fde2362b084e7f079b)
1aff8795cSHeiko Stübner /*
2aff8795cSHeiko Stübner  * (C) Copyright 2015 Google, Inc
3aff8795cSHeiko Stübner  *
4aff8795cSHeiko Stübner  * SPDX-License-Identifier:	GPL-2.0
5aff8795cSHeiko Stübner  */
6aff8795cSHeiko Stübner 
7aff8795cSHeiko Stübner #include <common.h>
8aff8795cSHeiko Stübner #include <clk-uclass.h>
9aff8795cSHeiko Stübner #include <dm.h>
10aff8795cSHeiko Stübner #include <errno.h>
11aff8795cSHeiko Stübner #include <syscon.h>
12aff8795cSHeiko Stübner #include <asm/io.h>
13aff8795cSHeiko Stübner #include <asm/arch/clock.h>
14aff8795cSHeiko Stübner #include <asm/arch/cru_rk3036.h>
15aff8795cSHeiko Stübner #include <asm/arch/hardware.h>
16aff8795cSHeiko Stübner #include <dm/lists.h>
17aff8795cSHeiko Stübner #include <dt-bindings/clock/rk3036-cru.h>
18abd0128eSHeiko Stübner #include <linux/log2.h>
19aff8795cSHeiko Stübner 
20aff8795cSHeiko Stübner DECLARE_GLOBAL_DATA_PTR;
21aff8795cSHeiko Stübner 
22aff8795cSHeiko Stübner enum {
23aff8795cSHeiko Stübner 	VCO_MAX_HZ	= 2400U * 1000000,
24aff8795cSHeiko Stübner 	VCO_MIN_HZ	= 600 * 1000000,
25aff8795cSHeiko Stübner 	OUTPUT_MAX_HZ	= 2400U * 1000000,
26aff8795cSHeiko Stübner 	OUTPUT_MIN_HZ	= 24 * 1000000,
27aff8795cSHeiko Stübner };
28aff8795cSHeiko Stübner 
293a1c76d9SElaine Zhang #ifndef CONFIG_SPL_BUILD
303a1c76d9SElaine Zhang #define RK3036_CLK_DUMP(_id, _name, _iscru)	\
313a1c76d9SElaine Zhang {						\
323a1c76d9SElaine Zhang 	.id = _id,				\
333a1c76d9SElaine Zhang 	.name = _name,				\
343a1c76d9SElaine Zhang 	.is_cru = _iscru,			\
353a1c76d9SElaine Zhang }
363a1c76d9SElaine Zhang 
373a1c76d9SElaine Zhang static const struct rk3036_clk_info clks_dump[] = {
383a1c76d9SElaine Zhang 	RK3036_CLK_DUMP(PLL_APLL, "apll", true),
393a1c76d9SElaine Zhang 	RK3036_CLK_DUMP(PLL_DPLL, "dpll", true),
403a1c76d9SElaine Zhang 	RK3036_CLK_DUMP(PLL_GPLL, "gpll", true),
413a1c76d9SElaine Zhang };
423a1c76d9SElaine Zhang #endif
433a1c76d9SElaine Zhang 
44aff8795cSHeiko Stübner #define RATE_TO_DIV(input_rate, output_rate) \
45aff8795cSHeiko Stübner 	((input_rate) / (output_rate) - 1);
46aff8795cSHeiko Stübner 
47aff8795cSHeiko Stübner #define DIV_TO_RATE(input_rate, div)	((input_rate) / ((div) + 1))
48aff8795cSHeiko Stübner 
49aff8795cSHeiko Stübner #define PLL_DIVISORS(hz, _refdiv, _postdiv1, _postdiv2) {\
50aff8795cSHeiko Stübner 	.refdiv = _refdiv,\
51aff8795cSHeiko Stübner 	.fbdiv = (u32)((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ),\
52aff8795cSHeiko Stübner 	.postdiv1 = _postdiv1, .postdiv2 = _postdiv2};\
53aff8795cSHeiko Stübner 	_Static_assert(((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ) *\
54aff8795cSHeiko Stübner 			 OSC_HZ / (_refdiv * _postdiv1 * _postdiv2) == hz,\
55aff8795cSHeiko Stübner 			 #hz "Hz cannot be hit with PLL "\
56aff8795cSHeiko Stübner 			 "divisors on line " __stringify(__LINE__));
57aff8795cSHeiko Stübner 
586a464d9cSKever Yang /* use integer mode*/
59aff8795cSHeiko Stübner static const struct pll_div apll_init_cfg = PLL_DIVISORS(APLL_HZ, 1, 3, 1);
60aff8795cSHeiko Stübner static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2, 1);
61aff8795cSHeiko Stübner 
62aff8795cSHeiko Stübner static int rkclk_set_pll(struct rk3036_cru *cru, enum rk_clk_id clk_id,
63aff8795cSHeiko Stübner 			 const struct pll_div *div)
64aff8795cSHeiko Stübner {
65aff8795cSHeiko Stübner 	int pll_id = rk_pll_id(clk_id);
66aff8795cSHeiko Stübner 	struct rk3036_pll *pll = &cru->pll[pll_id];
67aff8795cSHeiko Stübner 
68aff8795cSHeiko Stübner 	/* All PLLs have same VCO and output frequency range restrictions. */
69aff8795cSHeiko Stübner 	uint vco_hz = OSC_HZ / 1000 * div->fbdiv / div->refdiv * 1000;
70aff8795cSHeiko Stübner 	uint output_hz = vco_hz / div->postdiv1 / div->postdiv2;
71aff8795cSHeiko Stübner 
72aff8795cSHeiko Stübner 	debug("PLL at %p: fbdiv=%d, refdiv=%d, postdiv1=%d, postdiv2=%d,\
73aff8795cSHeiko Stübner 		 vco=%u Hz, output=%u Hz\n",
74aff8795cSHeiko Stübner 			pll, div->fbdiv, div->refdiv, div->postdiv1,
75aff8795cSHeiko Stübner 			div->postdiv2, vco_hz, output_hz);
76aff8795cSHeiko Stübner 	assert(vco_hz >= VCO_MIN_HZ && vco_hz <= VCO_MAX_HZ &&
77aff8795cSHeiko Stübner 	       output_hz >= OUTPUT_MIN_HZ && output_hz <= OUTPUT_MAX_HZ);
78aff8795cSHeiko Stübner 
796a464d9cSKever Yang 	/* use integer mode */
806a464d9cSKever Yang 	rk_setreg(&pll->con1, 1 << PLL_DSMPD_SHIFT);
81aff8795cSHeiko Stübner 
8200fbb281SDavid Wu 	/* Power down */
8300fbb281SDavid Wu 	rk_setreg(&pll->con1, 1 << PLL_PD_SHIFT);
8400fbb281SDavid Wu 
85aff8795cSHeiko Stübner 	rk_clrsetreg(&pll->con0,
8637943aaeSKever Yang 		     PLL_POSTDIV1_MASK | PLL_FBDIV_MASK,
87aff8795cSHeiko Stübner 		     (div->postdiv1 << PLL_POSTDIV1_SHIFT) | div->fbdiv);
8837943aaeSKever Yang 	rk_clrsetreg(&pll->con1, PLL_POSTDIV2_MASK | PLL_REFDIV_MASK,
89aff8795cSHeiko Stübner 		     (div->postdiv2 << PLL_POSTDIV2_SHIFT |
90aff8795cSHeiko Stübner 		     div->refdiv << PLL_REFDIV_SHIFT));
91aff8795cSHeiko Stübner 
9200fbb281SDavid Wu 	/* Power Up */
9300fbb281SDavid Wu 	rk_clrreg(&pll->con1, 1 << PLL_PD_SHIFT);
9400fbb281SDavid Wu 
95aff8795cSHeiko Stübner 	/* waiting for pll lock */
96aff8795cSHeiko Stübner 	while (readl(&pll->con1) & (1 << PLL_LOCK_STATUS_SHIFT))
97aff8795cSHeiko Stübner 		udelay(1);
98aff8795cSHeiko Stübner 
99aff8795cSHeiko Stübner 	return 0;
100aff8795cSHeiko Stübner }
101aff8795cSHeiko Stübner 
102aff8795cSHeiko Stübner static void rkclk_init(struct rk3036_cru *cru)
103aff8795cSHeiko Stübner {
104aff8795cSHeiko Stübner 	u32 aclk_div;
105aff8795cSHeiko Stübner 	u32 hclk_div;
106aff8795cSHeiko Stübner 	u32 pclk_div;
107904b267dSElaine Zhang 	u32 nandc_div;
108aff8795cSHeiko Stübner 
109aff8795cSHeiko Stübner 	/* pll enter slow-mode */
110aff8795cSHeiko Stübner 	rk_clrsetreg(&cru->cru_mode_con,
11137943aaeSKever Yang 		     GPLL_MODE_MASK | APLL_MODE_MASK,
112aff8795cSHeiko Stübner 		     GPLL_MODE_SLOW << GPLL_MODE_SHIFT |
113aff8795cSHeiko Stübner 		     APLL_MODE_SLOW << APLL_MODE_SHIFT);
114aff8795cSHeiko Stübner 
115aff8795cSHeiko Stübner 	/* init pll */
116aff8795cSHeiko Stübner 	rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg);
117aff8795cSHeiko Stübner 	rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);
118aff8795cSHeiko Stübner 
119aff8795cSHeiko Stübner 	/*
12037943aaeSKever Yang 	 * select apll as cpu/core clock pll source and
12137943aaeSKever Yang 	 * set up dependent divisors for PERI and ACLK clocks.
122aff8795cSHeiko Stübner 	 * core hz : apll = 1:1
123aff8795cSHeiko Stübner 	 */
124aff8795cSHeiko Stübner 	aclk_div = APLL_HZ / CORE_ACLK_HZ - 1;
125f149c047SElaine Zhang 	assert((aclk_div + 1) * CORE_ACLK_HZ <= APLL_HZ && aclk_div < 0x7);
126aff8795cSHeiko Stübner 
127aff8795cSHeiko Stübner 	pclk_div = APLL_HZ / CORE_PERI_HZ - 1;
128f149c047SElaine Zhang 	assert((pclk_div + 1) * CORE_PERI_HZ <= APLL_HZ && pclk_div < 0xf);
129aff8795cSHeiko Stübner 
130aff8795cSHeiko Stübner 	rk_clrsetreg(&cru->cru_clksel_con[0],
13137943aaeSKever Yang 		     CORE_CLK_PLL_SEL_MASK | CORE_DIV_CON_MASK,
132aff8795cSHeiko Stübner 		     CORE_CLK_PLL_SEL_APLL << CORE_CLK_PLL_SEL_SHIFT |
133aff8795cSHeiko Stübner 		     0 << CORE_DIV_CON_SHIFT);
134aff8795cSHeiko Stübner 
135aff8795cSHeiko Stübner 	rk_clrsetreg(&cru->cru_clksel_con[1],
13637943aaeSKever Yang 		     CORE_ACLK_DIV_MASK | CORE_PERI_DIV_MASK,
137aff8795cSHeiko Stübner 		     aclk_div << CORE_ACLK_DIV_SHIFT |
138aff8795cSHeiko Stübner 		     pclk_div << CORE_PERI_DIV_SHIFT);
139aff8795cSHeiko Stübner 
140aff8795cSHeiko Stübner 	/*
141f54f0bc2SKever Yang 	 * select gpll as pd_bus bus clock source and
142aff8795cSHeiko Stübner 	 * set up dependent divisors for PCLK/HCLK and ACLK clocks.
143aff8795cSHeiko Stübner 	 */
1441960b010SKever Yang 	aclk_div = GPLL_HZ / BUS_ACLK_HZ - 1;
145f149c047SElaine Zhang 	assert((aclk_div + 1) * BUS_ACLK_HZ <= GPLL_HZ && aclk_div <= 0x1f);
146aff8795cSHeiko Stübner 
147f54f0bc2SKever Yang 	pclk_div = BUS_ACLK_HZ / BUS_PCLK_HZ - 1;
148f149c047SElaine Zhang 	assert((pclk_div + 1) * BUS_PCLK_HZ <= BUS_ACLK_HZ && pclk_div <= 0x7);
149aff8795cSHeiko Stübner 
150f54f0bc2SKever Yang 	hclk_div = BUS_ACLK_HZ / BUS_HCLK_HZ - 1;
151f149c047SElaine Zhang 	assert((hclk_div + 1) * BUS_HCLK_HZ <= BUS_ACLK_HZ && hclk_div <= 0x3);
152aff8795cSHeiko Stübner 
153aff8795cSHeiko Stübner 	rk_clrsetreg(&cru->cru_clksel_con[0],
15437943aaeSKever Yang 		     BUS_ACLK_PLL_SEL_MASK | BUS_ACLK_DIV_MASK,
1551960b010SKever Yang 		     BUS_ACLK_PLL_SEL_GPLL << BUS_ACLK_PLL_SEL_SHIFT |
15637943aaeSKever Yang 		     aclk_div << BUS_ACLK_DIV_SHIFT);
157aff8795cSHeiko Stübner 
158aff8795cSHeiko Stübner 	rk_clrsetreg(&cru->cru_clksel_con[1],
15937943aaeSKever Yang 		     BUS_PCLK_DIV_MASK | BUS_HCLK_DIV_MASK,
16037943aaeSKever Yang 		     pclk_div << BUS_PCLK_DIV_SHIFT |
16137943aaeSKever Yang 		     hclk_div << BUS_HCLK_DIV_SHIFT);
162aff8795cSHeiko Stübner 
163aff8795cSHeiko Stübner 	/*
16437943aaeSKever Yang 	 * select gpll as pd_peri bus clock source and
165aff8795cSHeiko Stübner 	 * set up dependent divisors for PCLK/HCLK and ACLK clocks.
166aff8795cSHeiko Stübner 	 */
167aff8795cSHeiko Stübner 	aclk_div = GPLL_HZ / PERI_ACLK_HZ - 1;
168f149c047SElaine Zhang 	assert((aclk_div + 1) * PERI_ACLK_HZ <= GPLL_HZ && aclk_div < 0x1f);
169aff8795cSHeiko Stübner 
170abd0128eSHeiko Stübner 	hclk_div = ilog2(PERI_ACLK_HZ / PERI_HCLK_HZ);
171f149c047SElaine Zhang 	assert((1 << hclk_div) * PERI_HCLK_HZ <=
1721960b010SKever Yang 		PERI_ACLK_HZ && (hclk_div < 0x4));
173aff8795cSHeiko Stübner 
174abd0128eSHeiko Stübner 	pclk_div = ilog2(PERI_ACLK_HZ / PERI_PCLK_HZ);
175f149c047SElaine Zhang 	assert((1 << pclk_div) * PERI_PCLK_HZ <=
176aff8795cSHeiko Stübner 		PERI_ACLK_HZ && pclk_div < 0x8);
177aff8795cSHeiko Stübner 
178aff8795cSHeiko Stübner 	rk_clrsetreg(&cru->cru_clksel_con[10],
17937943aaeSKever Yang 		     PERI_PLL_SEL_MASK | PERI_PCLK_DIV_MASK |
18037943aaeSKever Yang 		     PERI_HCLK_DIV_MASK | PERI_ACLK_DIV_MASK,
181aff8795cSHeiko Stübner 		     PERI_PLL_GPLL << PERI_PLL_SEL_SHIFT |
182aff8795cSHeiko Stübner 		     pclk_div << PERI_PCLK_DIV_SHIFT |
183aff8795cSHeiko Stübner 		     hclk_div << PERI_HCLK_DIV_SHIFT |
184aff8795cSHeiko Stübner 		     aclk_div << PERI_ACLK_DIV_SHIFT);
185aff8795cSHeiko Stübner 
186904b267dSElaine Zhang 	nandc_div = DIV_ROUND_UP(GPLL_HZ, 150 * 1000000);
187904b267dSElaine Zhang 
188904b267dSElaine Zhang 	rk_clrsetreg(&cru->cru_clksel_con[16],
189904b267dSElaine Zhang 		     NANDC_PLL_MASK | NANDC_DIV_MASK,
190904b267dSElaine Zhang 		     NANDC_SEL_GPLL << NANDC_PLL_SHIFT |
191904b267dSElaine Zhang 		     nandc_div << NANDC_DIV_SHIFT);
192904b267dSElaine Zhang 
193aff8795cSHeiko Stübner 	/* PLL enter normal-mode */
194aff8795cSHeiko Stübner 	rk_clrsetreg(&cru->cru_mode_con,
19537943aaeSKever Yang 		     GPLL_MODE_MASK | APLL_MODE_MASK,
196aff8795cSHeiko Stübner 		     GPLL_MODE_NORM << GPLL_MODE_SHIFT |
197aff8795cSHeiko Stübner 		     APLL_MODE_NORM << APLL_MODE_SHIFT);
198aff8795cSHeiko Stübner }
199aff8795cSHeiko Stübner 
200aff8795cSHeiko Stübner /* Get pll rate by id */
201aff8795cSHeiko Stübner static uint32_t rkclk_pll_get_rate(struct rk3036_cru *cru,
202aff8795cSHeiko Stübner 				   enum rk_clk_id clk_id)
203aff8795cSHeiko Stübner {
204aff8795cSHeiko Stübner 	uint32_t refdiv, fbdiv, postdiv1, postdiv2;
205aff8795cSHeiko Stübner 	uint32_t con;
206aff8795cSHeiko Stübner 	int pll_id = rk_pll_id(clk_id);
207aff8795cSHeiko Stübner 	struct rk3036_pll *pll = &cru->pll[pll_id];
208aff8795cSHeiko Stübner 	static u8 clk_shift[CLK_COUNT] = {
209aff8795cSHeiko Stübner 		0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, 0xff,
210aff8795cSHeiko Stübner 		GPLL_MODE_SHIFT, 0xff
211aff8795cSHeiko Stübner 	};
21237943aaeSKever Yang 	static u32 clk_mask[CLK_COUNT] = {
21337943aaeSKever Yang 		0xffffffff, APLL_MODE_MASK, DPLL_MODE_MASK, 0xffffffff,
21437943aaeSKever Yang 		GPLL_MODE_MASK, 0xffffffff
215aff8795cSHeiko Stübner 	};
216aff8795cSHeiko Stübner 	uint shift;
217aff8795cSHeiko Stübner 	uint mask;
218aff8795cSHeiko Stübner 
219aff8795cSHeiko Stübner 	con = readl(&cru->cru_mode_con);
220aff8795cSHeiko Stübner 	shift = clk_shift[clk_id];
221aff8795cSHeiko Stübner 	mask = clk_mask[clk_id];
222aff8795cSHeiko Stübner 
22337943aaeSKever Yang 	switch ((con & mask) >> shift) {
224aff8795cSHeiko Stübner 	case GPLL_MODE_SLOW:
225aff8795cSHeiko Stübner 		return OSC_HZ;
226aff8795cSHeiko Stübner 	case GPLL_MODE_NORM:
227aff8795cSHeiko Stübner 
228aff8795cSHeiko Stübner 		/* normal mode */
229aff8795cSHeiko Stübner 		con = readl(&pll->con0);
23037943aaeSKever Yang 		postdiv1 = (con & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT;
23137943aaeSKever Yang 		fbdiv = (con & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT;
232aff8795cSHeiko Stübner 		con = readl(&pll->con1);
23337943aaeSKever Yang 		postdiv2 = (con & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT;
23437943aaeSKever Yang 		refdiv = (con & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT;
235aff8795cSHeiko Stübner 		return (24 * fbdiv / (refdiv * postdiv1 * postdiv2)) * 1000000;
236aff8795cSHeiko Stübner 	case GPLL_MODE_DEEP:
237aff8795cSHeiko Stübner 	default:
238aff8795cSHeiko Stübner 		return 32768;
239aff8795cSHeiko Stübner 	}
240aff8795cSHeiko Stübner }
241aff8795cSHeiko Stübner 
242aff8795cSHeiko Stübner static ulong rockchip_mmc_get_clk(struct rk3036_cru *cru, uint clk_general_rate,
243aff8795cSHeiko Stübner 				  int periph)
244aff8795cSHeiko Stübner {
245aff8795cSHeiko Stübner 	uint src_rate;
246aff8795cSHeiko Stübner 	uint div, mux;
247aff8795cSHeiko Stübner 	u32 con;
248aff8795cSHeiko Stübner 
249aff8795cSHeiko Stübner 	switch (periph) {
250aff8795cSHeiko Stübner 	case HCLK_EMMC:
2517f0cfe47SXu Ziyuan 	case SCLK_EMMC:
252aff8795cSHeiko Stübner 		con = readl(&cru->cru_clksel_con[12]);
25337943aaeSKever Yang 		mux = (con & EMMC_PLL_MASK) >> EMMC_PLL_SHIFT;
25437943aaeSKever Yang 		div = (con & EMMC_DIV_MASK) >> EMMC_DIV_SHIFT;
255aff8795cSHeiko Stübner 		break;
256aff8795cSHeiko Stübner 	case HCLK_SDIO:
2577f0cfe47SXu Ziyuan 	case SCLK_SDIO:
258aff8795cSHeiko Stübner 		con = readl(&cru->cru_clksel_con[12]);
25937943aaeSKever Yang 		mux = (con & MMC0_PLL_MASK) >> MMC0_PLL_SHIFT;
26037943aaeSKever Yang 		div = (con & MMC0_DIV_MASK) >> MMC0_DIV_SHIFT;
261aff8795cSHeiko Stübner 		break;
262aff8795cSHeiko Stübner 	default:
263aff8795cSHeiko Stübner 		return -EINVAL;
264aff8795cSHeiko Stübner 	}
265aff8795cSHeiko Stübner 
266aff8795cSHeiko Stübner 	src_rate = mux == EMMC_SEL_24M ? OSC_HZ : clk_general_rate;
2673a94d75dSKever Yang 	return DIV_TO_RATE(src_rate, div) / 2;
268aff8795cSHeiko Stübner }
269aff8795cSHeiko Stübner 
270aff8795cSHeiko Stübner static ulong rockchip_mmc_set_clk(struct rk3036_cru *cru, uint clk_general_rate,
271aff8795cSHeiko Stübner 				  int periph, uint freq)
272aff8795cSHeiko Stübner {
273aff8795cSHeiko Stübner 	int src_clk_div;
274aff8795cSHeiko Stübner 	int mux;
275aff8795cSHeiko Stübner 
276aff8795cSHeiko Stübner 	debug("%s: clk_general_rate=%u\n", __func__, clk_general_rate);
277aff8795cSHeiko Stübner 
278aff8795cSHeiko Stübner 	/* mmc clock auto divide 2 in internal */
2793a94d75dSKever Yang 	src_clk_div = DIV_ROUND_UP(clk_general_rate / 2, freq);
280aff8795cSHeiko Stübner 
281217273cdSKever Yang 	if (src_clk_div > 128) {
2823a94d75dSKever Yang 		src_clk_div = DIV_ROUND_UP(OSC_HZ / 2, freq);
283217273cdSKever Yang 		assert(src_clk_div - 1 < 128);
284aff8795cSHeiko Stübner 		mux = EMMC_SEL_24M;
285aff8795cSHeiko Stübner 	} else {
286aff8795cSHeiko Stübner 		mux = EMMC_SEL_GPLL;
287aff8795cSHeiko Stübner 	}
288aff8795cSHeiko Stübner 
289aff8795cSHeiko Stübner 	switch (periph) {
290aff8795cSHeiko Stübner 	case HCLK_EMMC:
2917f0cfe47SXu Ziyuan 	case SCLK_EMMC:
292aff8795cSHeiko Stübner 		rk_clrsetreg(&cru->cru_clksel_con[12],
29337943aaeSKever Yang 			     EMMC_PLL_MASK | EMMC_DIV_MASK,
294aff8795cSHeiko Stübner 			     mux << EMMC_PLL_SHIFT |
295aff8795cSHeiko Stübner 			     (src_clk_div - 1) << EMMC_DIV_SHIFT);
296aff8795cSHeiko Stübner 		break;
297aff8795cSHeiko Stübner 	case HCLK_SDIO:
2987f0cfe47SXu Ziyuan 	case SCLK_SDIO:
299aff8795cSHeiko Stübner 		rk_clrsetreg(&cru->cru_clksel_con[11],
30037943aaeSKever Yang 			     MMC0_PLL_MASK | MMC0_DIV_MASK,
301aff8795cSHeiko Stübner 			     mux << MMC0_PLL_SHIFT |
302aff8795cSHeiko Stübner 			     (src_clk_div - 1) << MMC0_DIV_SHIFT);
303aff8795cSHeiko Stübner 		break;
304aff8795cSHeiko Stübner 	default:
305aff8795cSHeiko Stübner 		return -EINVAL;
306aff8795cSHeiko Stübner 	}
307aff8795cSHeiko Stübner 
308aff8795cSHeiko Stübner 	return rockchip_mmc_get_clk(cru, clk_general_rate, periph);
309aff8795cSHeiko Stübner }
310aff8795cSHeiko Stübner 
311*df77e7a3SElaine Zhang static ulong rk3036_spi_get_clk(struct rk3036_cru *cru, uint clk_general_rate)
312*df77e7a3SElaine Zhang {
313*df77e7a3SElaine Zhang 	u32 div, con;
314*df77e7a3SElaine Zhang 
315*df77e7a3SElaine Zhang 	con = readl(&cru->cru_clksel_con[25]);
316*df77e7a3SElaine Zhang 	div = (con & SPI_DIV_MASK) >> SPI_DIV_SHIFT;
317*df77e7a3SElaine Zhang 
318*df77e7a3SElaine Zhang 	return DIV_TO_RATE(clk_general_rate, div);
319*df77e7a3SElaine Zhang }
320*df77e7a3SElaine Zhang 
321*df77e7a3SElaine Zhang static ulong  rk3036_spi_set_clk(struct rk3036_cru *cru,
322*df77e7a3SElaine Zhang 				 uint clk_general_rate,
323*df77e7a3SElaine Zhang 				 ulong hz)
324*df77e7a3SElaine Zhang {
325*df77e7a3SElaine Zhang 	int div;
326*df77e7a3SElaine Zhang 
327*df77e7a3SElaine Zhang 	div = DIV_ROUND_UP(clk_general_rate, hz);
328*df77e7a3SElaine Zhang 	assert(div - 1 < 128);
329*df77e7a3SElaine Zhang 	rk_clrsetreg(&cru->cru_clksel_con[25],
330*df77e7a3SElaine Zhang 		     SPI_PLL_SEL_MASK | SPI_DIV_MASK,
331*df77e7a3SElaine Zhang 		     SPI_PLL_SEL_GPLL << SPI_PLL_SEL_SHIFT |
332*df77e7a3SElaine Zhang 		     (div - 1) << SPI_DIV_SHIFT);
333*df77e7a3SElaine Zhang 	return  rk3036_spi_get_clk(cru, clk_general_rate);
334*df77e7a3SElaine Zhang }
335*df77e7a3SElaine Zhang 
33615ede2a1SFinley Xiao static ulong rockchip_dclk_lcdc_get_clk(struct rk3036_cru *cru,
33715ede2a1SFinley Xiao 					uint clk_general_rate)
33815ede2a1SFinley Xiao {
33915ede2a1SFinley Xiao 	u32 con, div, sel, parent;
34015ede2a1SFinley Xiao 
34115ede2a1SFinley Xiao 	con = readl(&cru->cru_clksel_con[28]);
34215ede2a1SFinley Xiao 	div = (con & LCDC_DCLK_DIV_MASK) >> LCDC_DCLK_DIV_SHIFT;
34315ede2a1SFinley Xiao 	sel = (con & LCDC_DCLK_SEL_MASK) >> LCDC_DCLK_SEL_SHIFT;
34415ede2a1SFinley Xiao 	if (sel == LCDC_DCLK_SEL_GPLL)
34515ede2a1SFinley Xiao 		parent = clk_general_rate;
34615ede2a1SFinley Xiao 	else
34715ede2a1SFinley Xiao 		return -ENOENT;
34815ede2a1SFinley Xiao 
34915ede2a1SFinley Xiao 	return DIV_TO_RATE(parent, div);
35015ede2a1SFinley Xiao }
35115ede2a1SFinley Xiao 
35215ede2a1SFinley Xiao static ulong rockchip_dclk_lcdc_set_clk(struct rk3036_cru *cru,
35315ede2a1SFinley Xiao 					uint clk_general_rate, uint freq)
35415ede2a1SFinley Xiao {
35515ede2a1SFinley Xiao 	int src_clk_div;
35615ede2a1SFinley Xiao 
35715ede2a1SFinley Xiao 	src_clk_div = DIV_ROUND_UP(clk_general_rate, freq);
35815ede2a1SFinley Xiao 	assert(src_clk_div - 1 <= 255);
35915ede2a1SFinley Xiao 
36015ede2a1SFinley Xiao 	rk_clrsetreg(&cru->cru_clksel_con[28],
36115ede2a1SFinley Xiao 		     LCDC_DCLK_SEL_MASK | LCDC_DCLK_DIV_MASK,
36215ede2a1SFinley Xiao 		     LCDC_DCLK_SEL_GPLL << LCDC_DCLK_SEL_SHIFT |
36315ede2a1SFinley Xiao 		     (src_clk_div - 1) << LCDC_DCLK_DIV_SHIFT);
36415ede2a1SFinley Xiao 
36515ede2a1SFinley Xiao 	return rockchip_dclk_lcdc_get_clk(cru, clk_general_rate);
36615ede2a1SFinley Xiao }
36715ede2a1SFinley Xiao 
36815ede2a1SFinley Xiao static ulong rockchip_aclk_lcdc_get_clk(struct rk3036_cru *cru,
36915ede2a1SFinley Xiao 					uint clk_general_rate)
37015ede2a1SFinley Xiao {
37115ede2a1SFinley Xiao 	u32 con, div, sel, parent;
37215ede2a1SFinley Xiao 
37315ede2a1SFinley Xiao 	con = readl(&cru->cru_clksel_con[31]);
37415ede2a1SFinley Xiao 	div = (con & LCDC_ACLK_DIV_MASK) >> LCDC_ACLK_DIV_SHIFT;
37515ede2a1SFinley Xiao 	sel = (con & LCDC_ACLK_SEL_MASK) >> LCDC_ACLK_SEL_SHIFT;
37615ede2a1SFinley Xiao 	if (sel == LCDC_ACLK_SEL_GPLL)
37715ede2a1SFinley Xiao 		parent = clk_general_rate;
37815ede2a1SFinley Xiao 	else
37915ede2a1SFinley Xiao 		return -ENOENT;
38015ede2a1SFinley Xiao 
38115ede2a1SFinley Xiao 	return DIV_TO_RATE(parent, div);
38215ede2a1SFinley Xiao }
38315ede2a1SFinley Xiao 
38415ede2a1SFinley Xiao static ulong rockchip_aclk_lcdc_set_clk(struct rk3036_cru *cru,
38515ede2a1SFinley Xiao 					uint clk_general_rate, uint freq)
38615ede2a1SFinley Xiao {
38715ede2a1SFinley Xiao 	int src_clk_div;
38815ede2a1SFinley Xiao 
38915ede2a1SFinley Xiao 	src_clk_div = DIV_ROUND_UP(clk_general_rate, freq);
39015ede2a1SFinley Xiao 	assert(src_clk_div - 1 <= 31);
39115ede2a1SFinley Xiao 
39215ede2a1SFinley Xiao 	rk_clrsetreg(&cru->cru_clksel_con[31],
39315ede2a1SFinley Xiao 		     LCDC_ACLK_SEL_MASK | LCDC_ACLK_DIV_MASK,
39415ede2a1SFinley Xiao 		     LCDC_ACLK_SEL_GPLL << LCDC_ACLK_SEL_SHIFT |
39515ede2a1SFinley Xiao 		     (src_clk_div - 1) << LCDC_ACLK_DIV_SHIFT);
39615ede2a1SFinley Xiao 
39715ede2a1SFinley Xiao 	return rockchip_aclk_lcdc_get_clk(cru, clk_general_rate);
39815ede2a1SFinley Xiao }
39915ede2a1SFinley Xiao 
400aff8795cSHeiko Stübner static ulong rk3036_clk_get_rate(struct clk *clk)
401aff8795cSHeiko Stübner {
402aff8795cSHeiko Stübner 	struct rk3036_clk_priv *priv = dev_get_priv(clk->dev);
40315ede2a1SFinley Xiao 	ulong gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL);
404aff8795cSHeiko Stübner 
405aff8795cSHeiko Stübner 	switch (clk->id) {
406aff8795cSHeiko Stübner 	case 0 ... 63:
407aff8795cSHeiko Stübner 		return rkclk_pll_get_rate(priv->cru, clk->id);
40815ede2a1SFinley Xiao 	case SCLK_LCDC:
40915ede2a1SFinley Xiao 		return rockchip_dclk_lcdc_get_clk(priv->cru, gclk_rate);
41015ede2a1SFinley Xiao 	case ACLK_LCDC:
41115ede2a1SFinley Xiao 		return rockchip_aclk_lcdc_get_clk(priv->cru, gclk_rate);
412*df77e7a3SElaine Zhang 	case SCLK_SPI:
413*df77e7a3SElaine Zhang 		return rk3036_spi_get_clk(priv->cru, gclk_rate);
414aff8795cSHeiko Stübner 	default:
415aff8795cSHeiko Stübner 		return -ENOENT;
416aff8795cSHeiko Stübner 	}
417aff8795cSHeiko Stübner }
418aff8795cSHeiko Stübner 
419aff8795cSHeiko Stübner static ulong rk3036_clk_set_rate(struct clk *clk, ulong rate)
420aff8795cSHeiko Stübner {
421aff8795cSHeiko Stübner 	struct rk3036_clk_priv *priv = dev_get_priv(clk->dev);
422aff8795cSHeiko Stübner 	ulong new_rate, gclk_rate;
423aff8795cSHeiko Stübner 
424aff8795cSHeiko Stübner 	gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL);
425aff8795cSHeiko Stübner 	switch (clk->id) {
426aff8795cSHeiko Stübner 	case 0 ... 63:
427aff8795cSHeiko Stübner 		return 0;
428aff8795cSHeiko Stübner 	case HCLK_EMMC:
4297f0cfe47SXu Ziyuan 	case SCLK_EMMC:
430aff8795cSHeiko Stübner 		new_rate = rockchip_mmc_set_clk(priv->cru, gclk_rate,
431aff8795cSHeiko Stübner 						clk->id, rate);
432aff8795cSHeiko Stübner 		break;
43315ede2a1SFinley Xiao 	case SCLK_LCDC:
43415ede2a1SFinley Xiao 		new_rate = rockchip_dclk_lcdc_set_clk(priv->cru, gclk_rate,
43515ede2a1SFinley Xiao 						      rate);
43615ede2a1SFinley Xiao 		break;
43715ede2a1SFinley Xiao 	case ACLK_LCDC:
43815ede2a1SFinley Xiao 		new_rate = rockchip_aclk_lcdc_set_clk(priv->cru, gclk_rate,
43915ede2a1SFinley Xiao 						      rate);
44015ede2a1SFinley Xiao 		break;
441*df77e7a3SElaine Zhang 	case SCLK_SPI:
442*df77e7a3SElaine Zhang 		new_rate = rk3036_spi_set_clk(priv->cru, gclk_rate,
443*df77e7a3SElaine Zhang 					      rate);
444*df77e7a3SElaine Zhang 		break;
445aff8795cSHeiko Stübner 	default:
446aff8795cSHeiko Stübner 		return -ENOENT;
447aff8795cSHeiko Stübner 	}
448aff8795cSHeiko Stübner 
449aff8795cSHeiko Stübner 	return new_rate;
450aff8795cSHeiko Stübner }
451aff8795cSHeiko Stübner 
452aff8795cSHeiko Stübner static struct clk_ops rk3036_clk_ops = {
453aff8795cSHeiko Stübner 	.get_rate	= rk3036_clk_get_rate,
454aff8795cSHeiko Stübner 	.set_rate	= rk3036_clk_set_rate,
455aff8795cSHeiko Stübner };
456aff8795cSHeiko Stübner 
457bb8e4ec3SDavid Wu static int rk3036_clk_ofdata_to_platdata(struct udevice *dev)
458bb8e4ec3SDavid Wu {
459bb8e4ec3SDavid Wu 	struct rk3036_clk_priv *priv = dev_get_priv(dev);
460bb8e4ec3SDavid Wu 
461bb8e4ec3SDavid Wu 	priv->cru = dev_read_addr_ptr(dev);
462bb8e4ec3SDavid Wu 
463bb8e4ec3SDavid Wu 	return 0;
464bb8e4ec3SDavid Wu }
465bb8e4ec3SDavid Wu 
466aff8795cSHeiko Stübner static int rk3036_clk_probe(struct udevice *dev)
467aff8795cSHeiko Stübner {
468aff8795cSHeiko Stübner 	struct rk3036_clk_priv *priv = dev_get_priv(dev);
469aff8795cSHeiko Stübner 
4703a1c76d9SElaine Zhang 	priv->sync_kernel = false;
4713a1c76d9SElaine Zhang 	if (!priv->armclk_enter_hz)
4723a1c76d9SElaine Zhang 		priv->armclk_enter_hz = rkclk_pll_get_rate(priv->cru,
4733a1c76d9SElaine Zhang 							   CLK_ARM);
474aff8795cSHeiko Stübner 	rkclk_init(priv->cru);
4753a1c76d9SElaine Zhang 	if (!priv->armclk_init_hz)
4763a1c76d9SElaine Zhang 		priv->armclk_init_hz = rkclk_pll_get_rate(priv->cru,
4773a1c76d9SElaine Zhang 							  CLK_ARM);
478aff8795cSHeiko Stübner 
479aff8795cSHeiko Stübner 	return 0;
480aff8795cSHeiko Stübner }
481aff8795cSHeiko Stübner 
482aff8795cSHeiko Stübner static int rk3036_clk_bind(struct udevice *dev)
483aff8795cSHeiko Stübner {
484aff8795cSHeiko Stübner 	int ret;
4853d555d75SElaine Zhang 	struct udevice *sys_child, *sf_child;
486fbdd1558SKever Yang 	struct sysreset_reg *priv;
4873d555d75SElaine Zhang 	struct softreset_reg *sf_priv;
488aff8795cSHeiko Stübner 
489aff8795cSHeiko Stübner 	/* The reset driver does not have a device node, so bind it here */
490fbdd1558SKever Yang 	ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
491fbdd1558SKever Yang 				 &sys_child);
492fbdd1558SKever Yang 	if (ret) {
493fbdd1558SKever Yang 		debug("Warning: No sysreset driver: ret=%d\n", ret);
494fbdd1558SKever Yang 	} else {
495fbdd1558SKever Yang 		priv = malloc(sizeof(struct sysreset_reg));
496fbdd1558SKever Yang 		priv->glb_srst_fst_value = offsetof(struct rk3036_cru,
497fbdd1558SKever Yang 						    cru_glb_srst_fst_value);
498fbdd1558SKever Yang 		priv->glb_srst_snd_value = offsetof(struct rk3036_cru,
499fbdd1558SKever Yang 						    cru_glb_srst_snd_value);
500fbdd1558SKever Yang 		sys_child->priv = priv;
501fbdd1558SKever Yang 	}
502aff8795cSHeiko Stübner 
5033d555d75SElaine Zhang 	ret = device_bind_driver_to_node(dev, "rockchip_reset", "reset",
5043d555d75SElaine Zhang 					 dev_ofnode(dev), &sf_child);
5053d555d75SElaine Zhang 	if (ret) {
5063d555d75SElaine Zhang 		debug("Warning: No rockchip reset driver: ret=%d\n", ret);
5073d555d75SElaine Zhang 	} else {
5083d555d75SElaine Zhang 		sf_priv = malloc(sizeof(struct softreset_reg));
5093d555d75SElaine Zhang 		sf_priv->sf_reset_offset = offsetof(struct rk3036_cru,
5103d555d75SElaine Zhang 						    cru_softrst_con[0]);
5113d555d75SElaine Zhang 		sf_priv->sf_reset_num = 9;
5123d555d75SElaine Zhang 		sf_child->priv = sf_priv;
5133d555d75SElaine Zhang 	}
5143d555d75SElaine Zhang 
515aff8795cSHeiko Stübner 	return 0;
516aff8795cSHeiko Stübner }
517aff8795cSHeiko Stübner 
518aff8795cSHeiko Stübner static const struct udevice_id rk3036_clk_ids[] = {
519aff8795cSHeiko Stübner 	{ .compatible = "rockchip,rk3036-cru" },
520aff8795cSHeiko Stübner 	{ }
521aff8795cSHeiko Stübner };
522aff8795cSHeiko Stübner 
52392ac73e4SSimon Glass U_BOOT_DRIVER(rockchip_rk3036_cru) = {
524aff8795cSHeiko Stübner 	.name		= "clk_rk3036",
525aff8795cSHeiko Stübner 	.id		= UCLASS_CLK,
526aff8795cSHeiko Stübner 	.of_match	= rk3036_clk_ids,
527aff8795cSHeiko Stübner 	.priv_auto_alloc_size = sizeof(struct rk3036_clk_priv),
528bb8e4ec3SDavid Wu 	.ofdata_to_platdata = rk3036_clk_ofdata_to_platdata,
529aff8795cSHeiko Stübner 	.ops		= &rk3036_clk_ops,
530aff8795cSHeiko Stübner 	.bind		= rk3036_clk_bind,
531aff8795cSHeiko Stübner 	.probe		= rk3036_clk_probe,
532aff8795cSHeiko Stübner };
5333a1c76d9SElaine Zhang 
5343a1c76d9SElaine Zhang #ifndef CONFIG_SPL_BUILD
5353a1c76d9SElaine Zhang /**
5363a1c76d9SElaine Zhang  * soc_clk_dump() - Print clock frequencies
5373a1c76d9SElaine Zhang  * Returns zero on success
5383a1c76d9SElaine Zhang  *
5393a1c76d9SElaine Zhang  * Implementation for the clk dump command.
5403a1c76d9SElaine Zhang  */
5413a1c76d9SElaine Zhang int soc_clk_dump(void)
5423a1c76d9SElaine Zhang {
5433a1c76d9SElaine Zhang 	struct udevice *cru_dev;
5443a1c76d9SElaine Zhang 	struct rk3036_clk_priv *priv;
5453a1c76d9SElaine Zhang 	const struct rk3036_clk_info *clk_dump;
5463a1c76d9SElaine Zhang 	struct clk clk;
5473a1c76d9SElaine Zhang 	unsigned long clk_count = ARRAY_SIZE(clks_dump);
5483a1c76d9SElaine Zhang 	unsigned long rate;
5493a1c76d9SElaine Zhang 	int i, ret;
5503a1c76d9SElaine Zhang 
5513a1c76d9SElaine Zhang 	ret = uclass_get_device_by_driver(UCLASS_CLK,
5523a1c76d9SElaine Zhang 					  DM_GET_DRIVER(rockchip_rk3036_cru),
5533a1c76d9SElaine Zhang 					  &cru_dev);
5543a1c76d9SElaine Zhang 	if (ret) {
5553a1c76d9SElaine Zhang 		printf("%s failed to get cru device\n", __func__);
5563a1c76d9SElaine Zhang 		return ret;
5573a1c76d9SElaine Zhang 	}
5583a1c76d9SElaine Zhang 
5593a1c76d9SElaine Zhang 	priv = dev_get_priv(cru_dev);
5603a1c76d9SElaine Zhang 	printf("CLK: (%s. arm: enter %lu KHz, init %lu KHz, kernel %lu%s)\n",
5613a1c76d9SElaine Zhang 	       priv->sync_kernel ? "sync kernel" : "uboot",
5623a1c76d9SElaine Zhang 	       priv->armclk_enter_hz / 1000,
5633a1c76d9SElaine Zhang 	       priv->armclk_init_hz / 1000,
5643a1c76d9SElaine Zhang 	       priv->set_armclk_rate ? priv->armclk_hz / 1000 : 0,
5653a1c76d9SElaine Zhang 	       priv->set_armclk_rate ? " KHz" : "N/A");
5663a1c76d9SElaine Zhang 	for (i = 0; i < clk_count; i++) {
5673a1c76d9SElaine Zhang 		clk_dump = &clks_dump[i];
5683a1c76d9SElaine Zhang 		if (clk_dump->name) {
5693a1c76d9SElaine Zhang 			clk.id = clk_dump->id;
5703a1c76d9SElaine Zhang 			if (clk_dump->is_cru)
5713a1c76d9SElaine Zhang 				ret = clk_request(cru_dev, &clk);
5723a1c76d9SElaine Zhang 			if (ret < 0)
5733a1c76d9SElaine Zhang 				return ret;
5743a1c76d9SElaine Zhang 
5753a1c76d9SElaine Zhang 			rate = clk_get_rate(&clk);
5763a1c76d9SElaine Zhang 			clk_free(&clk);
5773a1c76d9SElaine Zhang 			if (i == 0) {
5783a1c76d9SElaine Zhang 				if (rate < 0)
5793a1c76d9SElaine Zhang 					printf("  %s %s\n", clk_dump->name,
5803a1c76d9SElaine Zhang 					       "unknown");
5813a1c76d9SElaine Zhang 				else
5823a1c76d9SElaine Zhang 					printf("  %s %lu KHz\n", clk_dump->name,
5833a1c76d9SElaine Zhang 					       rate / 1000);
5843a1c76d9SElaine Zhang 			} else {
5853a1c76d9SElaine Zhang 				if (rate < 0)
5863a1c76d9SElaine Zhang 					printf("  %s %s\n", clk_dump->name,
5873a1c76d9SElaine Zhang 					       "unknown");
5883a1c76d9SElaine Zhang 				else
5893a1c76d9SElaine Zhang 					printf("  %s %lu KHz\n", clk_dump->name,
5903a1c76d9SElaine Zhang 					       rate / 1000);
5913a1c76d9SElaine Zhang 			}
5923a1c76d9SElaine Zhang 		}
5933a1c76d9SElaine Zhang 	}
5943a1c76d9SElaine Zhang 
5953a1c76d9SElaine Zhang 	return 0;
5963a1c76d9SElaine Zhang }
5973a1c76d9SElaine Zhang #endif
5983a1c76d9SElaine Zhang 
599