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