Message ID | 43cc4f16d77a5323e73d654ca9b73e3dda8bcf23.1502974487.git.paweljarosz3691@gmail.com |
---|---|
State | Changes Requested |
Delegated to: | Philipp Tomsich |
Headers | show |
On Thu, 17 Aug 2017, Paweł Jarosz wrote: > Add clock driver for rk3066 platform. > > Signed-off-by: Paweł Jarosz <paweljarosz3691@gmail.com> > Acked-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com> > --- > Changes since v1: > - updated to shifted masks > - moved clk init to tpl > > Changes since v2: > - none > > Changes since v3: > - none > > arch/arm/include/asm/arch-rockchip/cru_rk3066.h | 189 ++++++++ > drivers/clk/rockchip/Makefile | 1 + > drivers/clk/rockchip/clk_rk3066.c | 587 ++++++++++++++++++++++++ > 3 files changed, 777 insertions(+) > create mode 100644 arch/arm/include/asm/arch-rockchip/cru_rk3066.h > create mode 100644 drivers/clk/rockchip/clk_rk3066.c > > diff --git a/arch/arm/include/asm/arch-rockchip/cru_rk3066.h b/arch/arm/include/asm/arch-rockchip/cru_rk3066.h > new file mode 100644 > index 0000000..c4a6951 > --- /dev/null > +++ b/arch/arm/include/asm/arch-rockchip/cru_rk3066.h > @@ -0,0 +1,189 @@ > +/* > + * (C) Copyright 2016 Heiko Stuebner <heiko@sntech.de> > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > +#ifndef _ASM_ARCH_CRU_RK3066_H > +#define _ASM_ARCH_CRU_RK3066_H > + > +#define OSC_HZ (24 * 1000 * 1000) > + > +#define APLL_HZ (1416 * 1000000) > +#define APLL_SAFE_HZ (600 * 1000000) > +#define GPLL_HZ (594 * 1000000) > +#define CPLL_HZ (384 * 1000000) > + > +/* The SRAM is clocked off aclk_cpu, so we want to max it out for boot speed */ > +#define CPU_ACLK_HZ 297000000 > +#define CPU_HCLK_HZ 148500000 > +#define CPU_PCLK_HZ 74250000 > +#define CPU_H2P_HZ 74250000 > + > +#define PERI_ACLK_HZ 148500000 > +#define PERI_HCLK_HZ 148500000 > +#define PERI_PCLK_HZ 74250000 > + > +/* Private data for the clock driver - used by rockchip_get_cru() */ > +struct rk3066_clk_priv { > + struct rk3066_grf *grf; > + struct rk3066_cru *cru; > + ulong rate; I don't see the 'rate' field of the priv-structure ever used. Did I miss something? > + bool has_bwadj; I can't find any way to set 'has_bwadj' to 'true' in the below code. If this is not needed, has_bwadj should be removed/codepaths should be eliminated. If it is needed/recommended, it should always be used for those PLLs that support bandwidth adjustment. > +}; > + > +struct rk3066_cru { > + struct rk3066_pll { > + u32 con0; > + u32 con1; > + u32 con2; > + u32 con3; > + } pll[4]; > + u32 cru_mode_con; > + u32 cru_clksel_con[35]; > + u32 cru_clkgate_con[10]; > + u32 reserved1[2]; > + u32 cru_glb_srst_fst_value; > + u32 cru_glb_srst_snd_value; > + u32 reserved2[2]; > + u32 cru_softrst_con[9]; > + u32 cru_misc_con; > + u32 reserved3[2]; > + u32 cru_glb_cnt_th; > +}; > +check_member(rk3066_cru, cru_glb_cnt_th, 0x0140); > + > +/* CRU_CLKSEL0_CON */ > +enum { > + /* a9_core_div: core = core_src / (a9_core_div + 1) */ > + A9_CORE_DIV_SHIFT = 9, > + A9_CORE_DIV_MASK = 0x1f << A9_CORE_DIV_SHIFT, > + CORE_PLL_SHIFT = 8, > + CORE_PLL_MASK = 1 << CORE_PLL_SHIFT, > + CORE_PLL_SELECT_APLL = 0, > + CORE_PLL_SELECT_GPLL, > + > + /* core peri div: core:core_peri = 2:1, 4:1, 8:1 or 16:1 */ > + CORE_PERI_DIV_SHIFT = 6, > + CORE_PERI_DIV_MASK = 3 << CORE_PERI_DIV_SHIFT, > + > + /* aclk_cpu pll selection */ > + CPU_ACLK_PLL_SHIFT = 5, > + CPU_ACLK_PLL_MASK = 1 << CPU_ACLK_PLL_SHIFT, > + CPU_ACLK_PLL_SELECT_APLL = 0, > + CPU_ACLK_PLL_SELECT_GPLL, > + > + /* a9_cpu_div: aclk_cpu = cpu_src / (a9_cpu_div + 1) */ > + A9_CPU_DIV_SHIFT = 0, > + A9_CPU_DIV_MASK = 0x1f << A9_CPU_DIV_SHIFT, > +}; > + > +/* CRU_CLKSEL1_CON */ > +enum { > + /* ahb2apb_pclk_div: hclk_cpu:pclk_cpu = 1:1, 2:1 or 4:1 */ > + AHB2APB_DIV_SHIFT = 14, > + AHB2APB_DIV_MASK = 3 << AHB2APB_DIV_SHIFT, > + > + /* cpu_pclk_div: aclk_cpu:pclk_cpu = 1:1, 2:1, 4:1 or 8:1 */ > + CPU_PCLK_DIV_SHIFT = 12, > + CPU_PCLK_DIV_MASK = 3 << CPU_PCLK_DIV_SHIFT, > + > + /* cpu_hclk_div: aclk_cpu:hclk_cpu = 1:1, 2:1 or 4:1 */ > + CPU_HCLK_DIV_SHIFT = 8, > + CPU_HCLK_DIV_MASK = 3 << CPU_HCLK_DIV_SHIFT, > + > + /* core_aclk_div: cire:aclk_core = 1:1, 2:1, 3:1, 4:1 or 8:1 */ > + CORE_ACLK_DIV_SHIFT = 3, > + CORE_ACLK_DIV_MASK = 7 << CORE_ACLK_DIV_SHIFT, > +}; > + > +/* CRU_CLKSEL10_CON */ > +enum { > + PERI_SEL_PLL_SHIFT = 15, > + PERI_SEL_PLL_MASK = 1 << PERI_SEL_PLL_SHIFT, > + PERI_SEL_CPLL = 0, > + PERI_SEL_GPLL, > + > + /* peri pclk div: aclk_bus:pclk_bus = 1:1, 2:1, 4:1 or 8:1 */ > + PERI_PCLK_DIV_SHIFT = 12, > + PERI_PCLK_DIV_MASK = 3 << PERI_PCLK_DIV_SHIFT, > + > + /* peripheral bus hclk div:aclk_bus: hclk_bus = 1:1, 2:1 or 4:1 */ > + PERI_HCLK_DIV_SHIFT = 8, > + PERI_HCLK_DIV_MASK = 3 << PERI_HCLK_DIV_SHIFT, > + > + /* peri aclk div: aclk_peri = periph_src / (peri_aclk_div + 1) */ > + PERI_ACLK_DIV_SHIFT = 0, > + PERI_ACLK_DIV_MASK = 0x1f << PERI_ACLK_DIV_SHIFT, > +}; > +/* CRU_CLKSEL11_CON */ > +enum { > + MMC0_DIV_SHIFT = 0, > + MMC0_DIV_MASK = 0x3f << MMC0_DIV_SHIFT, > +}; > + > +/* CRU_CLKSEL12_CON */ > +enum { > + UART_PLL_SHIFT = 15, > + UART_PLL_MASK = 1 << UART_PLL_SHIFT, > + UART_PLL_SELECT_GENERAL = 0, > + UART_PLL_SELECT_CODEC, > + > + EMMC_DIV_SHIFT = 8, > + EMMC_DIV_MASK = 0x3f << EMMC_DIV_SHIFT, > + > + SDIO_DIV_SHIFT = 0, > + SDIO_DIV_MASK = 0x3f << SDIO_DIV_SHIFT, > +}; > + > +/* CRU_CLKSEL25_CON */ > +enum { > + SPI1_DIV_SHIFT = 8, > + SPI1_DIV_MASK = 0x7f << SPI1_DIV_SHIFT, > + > + SPI0_DIV_SHIFT = 0, > + SPI0_DIV_MASK = 0x7f << SPI0_DIV_SHIFT, > +}; > + > +/* CRU_MODE_CON */ > +enum { > + GPLL_MODE_SHIFT = 12, > + GPLL_MODE_MASK = 3 << GPLL_MODE_SHIFT, > + GPLL_MODE_SLOW = 0, > + GPLL_MODE_NORMAL, > + GPLL_MODE_DEEP, > + > + CPLL_MODE_SHIFT = 8, > + CPLL_MODE_MASK = 3 << CPLL_MODE_SHIFT, > + CPLL_MODE_SLOW = 0, > + CPLL_MODE_NORMAL, > + CPLL_MODE_DEEP, > + > + DPLL_MODE_SHIFT = 4, > + DPLL_MODE_MASK = 3 << DPLL_MODE_SHIFT, > + DPLL_MODE_SLOW = 0, > + DPLL_MODE_NORMAL, > + DPLL_MODE_DEEP, > + > + APLL_MODE_SHIFT = 0, > + APLL_MODE_MASK = 3 << APLL_MODE_SHIFT, > + APLL_MODE_SLOW = 0, > + APLL_MODE_NORMAL, > + APLL_MODE_DEEP, > +}; > + > +/* CRU_APLL_CON0 */ > +enum { > + CLKR_SHIFT = 8, > + CLKR_MASK = 0x3f << CLKR_SHIFT, > + > + CLKOD_SHIFT = 0, > + CLKOD_MASK = 0x3f << CLKOD_SHIFT, > +}; > + > +/* CRU_APLL_CON1 */ > +enum { > + CLKF_SHIFT = 0, > + CLKF_MASK = 0x1fff << CLKF_SHIFT, > +}; > + > +#endif > diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile > index c50aff2..167b08e 100644 > --- a/drivers/clk/rockchip/Makefile > +++ b/drivers/clk/rockchip/Makefile > @@ -5,6 +5,7 @@ > # > > obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o > +obj-$(CONFIG_ROCKCHIP_RK3066) += clk_rk3066.o > obj-$(CONFIG_ROCKCHIP_RK3188) += clk_rk3188.o > obj-$(CONFIG_ROCKCHIP_RK322X) += clk_rk322x.o > obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o > diff --git a/drivers/clk/rockchip/clk_rk3066.c b/drivers/clk/rockchip/clk_rk3066.c > new file mode 100644 > index 0000000..bcf65e0 > --- /dev/null > +++ b/drivers/clk/rockchip/clk_rk3066.c > @@ -0,0 +1,587 @@ > +/* > + * (C) Copyright 2015 Google, Inc > + * (C) Copyright 2016 Heiko Stuebner <heiko@sntech.de> > + * > + * SPDX-License-Identifier: GPL-2.0 > + */ > + > +#include <common.h> > +#include <clk-uclass.h> > +#include <dm.h> > +#include <dt-structs.h> > +#include <errno.h> > +#include <mapmem.h> > +#include <syscon.h> > +#include <asm/io.h> > +#include <asm/arch/clock.h> > +#include <asm/arch/cru_rk3066.h> > +#include <asm/arch/grf_rk3066.h> > +#include <asm/arch/hardware.h> > +#include <dt-bindings/clock/rk3066a-cru.h> > +#include <dm/device-internal.h> > +#include <dm/lists.h> > +#include <dm/uclass-internal.h> > +#include <linux/log2.h> > + > +DECLARE_GLOBAL_DATA_PTR; > + > +enum rk3066_clk_type { > + RK3066_CRU, > + RK3066A_CRU, > +}; > + > +struct rk3066_clk_plat { > +#if CONFIG_IS_ENABLED(OF_PLATDATA) > + struct dtd_rockchip_rk3066a_cru dtd; > +#endif > +}; > + > +struct pll_div { > + u32 nr; > + u32 nf; > + u32 no; > +}; > + > +enum { > + VCO_MAX_HZ = 1416U * 1000000, > + VCO_MIN_HZ = 300 * 1000000, > + OUTPUT_MAX_HZ = 1416U * 1000000, > + OUTPUT_MIN_HZ = 30 * 1000000, > + FREF_MAX_HZ = 1416U * 1000000, > + FREF_MIN_HZ = 30 * 1000, > +}; > + > +enum { > + /* PLL CON0 */ > + PLL_OD_MASK = 0x0f, > + > + /* PLL CON1 */ > + PLL_NF_MASK = 0x1fff, > + > + /* PLL CON2 */ > + PLL_BWADJ_MASK = 0x0fff, Please use GENMASK(..., ...). > + > + /* PLL CON3 */ > + PLL_RESET_SHIFT = 5, > + > + /* GRF_SOC_STATUS0 */ > + SOCSTS_DPLL_LOCK = 1 << 4, > + SOCSTS_APLL_LOCK = 1 << 5, > + SOCSTS_CPLL_LOCK = 1 << 6, > + SOCSTS_GPLL_LOCK = 1 << 7, Please use BIT(...). > +}; > + > +#define RATE_TO_DIV(input_rate, output_rate) \ > + ((input_rate) / (output_rate) - 1); Kever just removed this from all other clock-drivers. Could you track this change in this clock driver (sorry...)? A separate, additional patch is fine, if you prefer. > + > +#define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1)) > + > +#define PLL_DIVISORS(hz, _nr, _no) {\ > + .nr = _nr, .nf = (u32)((u64)hz * _nr * _no / OSC_HZ), .no = _no};\ > + _Static_assert(((u64)hz * _nr * _no / OSC_HZ) * OSC_HZ /\ > + (_nr * _no) == hz, #hz "Hz cannot be hit with PLL "\ > + "divisors on line " __stringify(__LINE__)); > + > +/* Keep divisors as low as possible to reduce jitter and power usage */ > +#ifdef CONFIG_TPL_BUILD > +static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2); > +static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 1, 2); > +#endif > + > +static int rkclk_set_pll(struct rk3066_cru *cru, enum rk_clk_id clk_id, > + const struct pll_div *div, bool has_bwadj) > +{ > + int pll_id = rk_pll_id(clk_id); > + struct rk3066_pll *pll = &cru->pll[pll_id]; > + /* All PLLs have same VCO and output frequency range restrictions. */ > + uint vco_hz = OSC_HZ / 1000 * div->nf / div->nr * 1000; > + uint output_hz = vco_hz / div->no; > + > + debug("PLL at %x: nf=%d, nr=%d, no=%d, vco=%u Hz, output=%u Hz\n", > + (uint)pll, div->nf, div->nr, div->no, 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 && > + (div->no == 1 || !(div->no % 2))); > + > + /* enter reset */ > + rk_setreg(&pll->con3, 1 << PLL_RESET_SHIFT); I'd prefer BIT(PLL_RESET_SHIFT). > + > + rk_clrsetreg(&pll->con0, > + CLKR_MASK | PLL_OD_MASK, > + ((div->nr - 1) << CLKR_SHIFT) | (div->no - 1)); > + rk_clrsetreg(&pll->con1, CLKF_MASK, div->nf - 1); > + > + if (has_bwadj) > + rk_clrsetreg(&pll->con2, PLL_BWADJ_MASK, (div->nf >> 1) - 1); > + > + udelay(10); Why the udelay() and not polling the PLL to signal that it locked/is stable? > + > + /* return from reset */ > + rk_clrreg(&pll->con3, 1 << PLL_RESET_SHIFT); Again, I would prefer BIT(PLL_RESET_SHIFT). > + > + return 0; > +} > + > +static int rkclk_configure_ddr(struct rk3066_cru *cru, struct rk3066_grf *grf, > + unsigned int hz, bool has_bwadj) > +{ > + static const struct pll_div dpll_cfg[] = { > + {.nf = 25, .nr = 2, .no = 1}, > + {.nf = 400, .nr = 9, .no = 2}, > + {.nf = 500, .nr = 9, .no = 2}, > + {.nf = 100, .nr = 3, .no = 1}, > + }; > + int cfg; > + > + switch (hz) { > + case 300000000: > + cfg = 0; > + break; > + case 533000000: /* actually 533.3P MHz */ > + cfg = 1; > + break; > + case 666000000: /* actually 666.6P MHz */ > + cfg = 2; > + break; > + case 800000000: > + cfg = 3; > + break; > + default: > + debug("Unsupported SDRAM frequency"); > + return -EINVAL; > + } > + > + /* pll enter slow-mode */ > + rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK, > + DPLL_MODE_SLOW << DPLL_MODE_SHIFT); > + > + rkclk_set_pll(cru, CLK_DDR, &dpll_cfg[cfg], has_bwadj); > + > + /* wait for pll lock */ > + while (!(readl(&grf->soc_status0) & SOCSTS_DPLL_LOCK)) > + udelay(1); > + > + /* PLL enter normal-mode */ > + rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK, > + DPLL_MODE_NORMAL << DPLL_MODE_SHIFT); > + > + return 0; > +} > + > +static int rkclk_configure_cpu(struct rk3066_cru *cru, struct rk3066_grf *grf, > + unsigned int hz, bool has_bwadj) > +{ > + static const struct pll_div apll_cfg[] = { > + {.nf = 50, .nr = 1, .no = 2}, > + {.nf = 59, .nr = 1, .no = 1}, > + }; > + int div_core_peri, div_aclk_core, cfg; > + > + /* > + * We support two possible frequencies, the safe 600MHz > + * which will work with default pmic settings and will > + * be set to get away from the 24MHz default and > + * the maximum of 1.416Ghz, which boards can set if they > + * were able to get pmic support for it. > + */ > + switch (hz) { > + case APLL_SAFE_HZ: > + cfg = 0; > + div_core_peri = 1; > + div_aclk_core = 3; > + break; > + case APLL_HZ: > + cfg = 1; > + div_core_peri = 2; > + div_aclk_core = 3; > + break; > + default: > + debug("Unsupported ARMCLK frequency"); > + return -EINVAL; > + } > + > + /* pll enter slow-mode */ > + rk_clrsetreg(&cru->cru_mode_con, APLL_MODE_MASK, > + APLL_MODE_SLOW << APLL_MODE_SHIFT); > + > + rkclk_set_pll(cru, CLK_ARM, &apll_cfg[cfg], has_bwadj); > + > + /* waiting for pll lock */ > + while (!(readl(&grf->soc_status0) & SOCSTS_APLL_LOCK)) > + udelay(1); > + > + /* Set divider for peripherals attached to the cpu core. */ > + rk_clrsetreg(&cru->cru_clksel_con[0], > + CORE_PERI_DIV_MASK, > + div_core_peri << CORE_PERI_DIV_SHIFT); > + > + /* set up dependent divisor for aclk_core */ > + rk_clrsetreg(&cru->cru_clksel_con[1], > + CORE_ACLK_DIV_MASK, > + div_aclk_core << CORE_ACLK_DIV_SHIFT); > + > + /* PLL enter normal-mode */ > + rk_clrsetreg(&cru->cru_mode_con, APLL_MODE_MASK, > + APLL_MODE_NORMAL << APLL_MODE_SHIFT); > + > + return hz; > +} > + > +/* Get pll rate by id */ > +static uint32_t rkclk_pll_get_rate(struct rk3066_cru *cru, > + enum rk_clk_id clk_id) > +{ > + uint32_t nr, no, nf; > + uint32_t con; > + int pll_id = rk_pll_id(clk_id); > + struct rk3066_pll *pll = &cru->pll[pll_id]; > + static u8 clk_shift[CLK_COUNT] = { > + 0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, CPLL_MODE_SHIFT, > + GPLL_MODE_SHIFT > + }; > + uint shift; > + > + con = readl(&cru->cru_mode_con); > + shift = clk_shift[clk_id]; > + switch ((con >> shift) & APLL_MODE_MASK >> APLL_MODE_SHIFT) { > + case APLL_MODE_SLOW: > + return OSC_HZ; > + case APLL_MODE_NORMAL: > + /* normal mode */ > + con = readl(&pll->con0); > + no = ((con >> CLKOD_SHIFT) & (CLKOD_MASK >> CLKOD_SHIFT)) + 1; Could you use 'bitfield_extract' from include/bitfield.h? > + nr = ((con >> CLKR_SHIFT) & (CLKR_MASK >> CLKR_SHIFT)) + 1; > + con = readl(&pll->con1); > + nf = ((con >> CLKF_SHIFT) & (CLKF_MASK >> CLKF_SHIFT)) + 1; > + Same. > + return (24 * nf / (nr * no)) * 1000000; Shouldn't this be (OSZ_HZ * nf) / (nr * no)? > + case APLL_MODE_DEEP: > + default: > + return 32768; > + } > +} > + > +static ulong rockchip_mmc_get_clk(struct rk3066_cru *cru, uint gclk_rate, > + int periph) > +{ > + uint div; > + u32 con; > + > + switch (periph) { > + case HCLK_EMMC: > + con = readl(&cru->cru_clksel_con[12]); > + div = (con >> EMMC_DIV_SHIFT) & EMMC_DIV_MASK >> EMMC_DIV_SHIFT; > + break; > + case HCLK_SDMMC: > + con = readl(&cru->cru_clksel_con[11]); > + div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK >> MMC0_DIV_SHIFT; > + break; > + case HCLK_SDIO: > + con = readl(&cru->cru_clksel_con[12]); > + div = (con >> SDIO_DIV_SHIFT) & SDIO_DIV_MASK >> SDIO_DIV_SHIFT; Again, can we use bitfield.h? > + break; > + default: > + return -EINVAL; > + } > + > + return DIV_TO_RATE(gclk_rate, div); > +} > + > +static ulong rockchip_mmc_set_clk(struct rk3066_cru *cru, uint gclk_rate, > + int periph, uint freq) > +{ > + int src_clk_div; > + > + debug("%s: gclk_rate=%u\n", __func__, gclk_rate); > + src_clk_div = RATE_TO_DIV(gclk_rate, freq); > + if (src_clk_div > 0x3f) > + src_clk_div = 0x3f; > + > + switch (periph) { > + case HCLK_EMMC: > + rk_clrsetreg(&cru->cru_clksel_con[12], > + EMMC_DIV_MASK, > + src_clk_div << EMMC_DIV_SHIFT); > + break; > + case HCLK_SDMMC: > + rk_clrsetreg(&cru->cru_clksel_con[11], > + MMC0_DIV_MASK, > + src_clk_div << MMC0_DIV_SHIFT); > + break; > + case HCLK_SDIO: > + rk_clrsetreg(&cru->cru_clksel_con[12], > + SDIO_DIV_MASK, > + src_clk_div << SDIO_DIV_SHIFT); > + break; > + default: > + return -EINVAL; > + } > + > + return rockchip_mmc_get_clk(cru, gclk_rate, periph); > +} > + > +static ulong rockchip_spi_get_clk(struct rk3066_cru *cru, uint gclk_rate, > + int periph) > +{ > + uint div; > + u32 con; > + > + switch (periph) { > + case SCLK_SPI0: > + con = readl(&cru->cru_clksel_con[25]); > + div = (con >> SPI0_DIV_SHIFT) & SPI0_DIV_MASK >> SPI0_DIV_SHIFT; > + break; > + case SCLK_SPI1: > + con = readl(&cru->cru_clksel_con[25]); > + div = (con >> SPI1_DIV_SHIFT) & SPI1_DIV_MASK >> SPI1_DIV_SHIFT; > + break; See above. > + default: > + return -EINVAL; > + } > + > + return DIV_TO_RATE(gclk_rate, div); > +} > + > +static ulong rockchip_spi_set_clk(struct rk3066_cru *cru, uint gclk_rate, > + int periph, uint freq) > +{ > + int src_clk_div = RATE_TO_DIV(gclk_rate, freq); > + > + switch (periph) { > + case SCLK_SPI0: > + assert(src_clk_div <= SPI0_DIV_MASK >> SPI0_DIV_SHIFT); > + rk_clrsetreg(&cru->cru_clksel_con[25], > + SPI0_DIV_MASK, > + src_clk_div << SPI0_DIV_SHIFT); > + break; > + case SCLK_SPI1: > + assert(src_clk_div <= SPI1_DIV_MASK >> SPI1_DIV_SHIFT); > + rk_clrsetreg(&cru->cru_clksel_con[25], > + SPI1_DIV_MASK, > + src_clk_div << SPI1_DIV_SHIFT); > + break; > + default: > + return -EINVAL; > + } > + > + return rockchip_spi_get_clk(cru, gclk_rate, periph); > +} > +#ifdef CONFIG_TPL_BUILD > +static void rkclk_init(struct rk3066_cru *cru, struct rk3066_grf *grf, > + bool has_bwadj) > +{ > + u32 aclk_div, hclk_div, pclk_div, h2p_div; > + > + /* pll enter slow-mode */ > + rk_clrsetreg(&cru->cru_mode_con, > + GPLL_MODE_MASK | > + CPLL_MODE_MASK, > + GPLL_MODE_SLOW << GPLL_MODE_SHIFT | > + CPLL_MODE_SLOW << CPLL_MODE_SHIFT); > + > + /* init pll */ > + rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg, has_bwadj); > + rkclk_set_pll(cru, CLK_CODEC, &cpll_init_cfg, has_bwadj); > + > + /* waiting for pll lock */ > + while ((readl(&grf->soc_status0) & > + (SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) != > + (SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) > + udelay(1); > + > + /* > + * cpu clock pll source selection and > + * reparent aclk_cpu_pre from apll to gpll > + * set up dependent divisors for PCLK/HCLK and ACLK clocks. > + */ > + aclk_div = RATE_TO_DIV(GPLL_HZ, CPU_ACLK_HZ); > + assert((aclk_div + 1) * CPU_ACLK_HZ == GPLL_HZ && aclk_div < 0x1f); > + > + rk_clrsetreg(&cru->cru_clksel_con[0], > + CPU_ACLK_PLL_MASK | > + A9_CPU_DIV_MASK, > + CPU_ACLK_PLL_SELECT_GPLL << CPU_ACLK_PLL_SHIFT | > + aclk_div << A9_CPU_DIV_SHIFT); > + > + hclk_div = ilog2(CPU_ACLK_HZ / CPU_HCLK_HZ); > + assert((1 << hclk_div) * CPU_HCLK_HZ == CPU_ACLK_HZ && hclk_div < 0x3); > + pclk_div = ilog2(CPU_ACLK_HZ / CPU_PCLK_HZ); > + assert((1 << pclk_div) * CPU_PCLK_HZ == CPU_ACLK_HZ && pclk_div < 0x4); > + h2p_div = ilog2(CPU_HCLK_HZ / CPU_H2P_HZ); > + assert((1 << h2p_div) * CPU_H2P_HZ == CPU_HCLK_HZ && pclk_div < 0x3); > + > + rk_clrsetreg(&cru->cru_clksel_con[1], > + AHB2APB_DIV_MASK | > + CPU_PCLK_DIV_MASK | > + CPU_HCLK_DIV_MASK, > + h2p_div << AHB2APB_DIV_SHIFT | > + pclk_div << CPU_PCLK_DIV_SHIFT | > + hclk_div << CPU_HCLK_DIV_SHIFT); > + > + /* > + * peri clock pll source selection and > + * 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); > + > + hclk_div = ilog2(PERI_ACLK_HZ / PERI_HCLK_HZ); > + assert((1 << hclk_div) * PERI_HCLK_HZ == > + PERI_ACLK_HZ && (hclk_div < 0x4)); > + > + pclk_div = ilog2(PERI_ACLK_HZ / PERI_PCLK_HZ); > + assert((1 << pclk_div) * PERI_PCLK_HZ == > + PERI_ACLK_HZ && (pclk_div < 0x4)); > + > + rk_clrsetreg(&cru->cru_clksel_con[10], > + PERI_PCLK_DIV_MASK | > + PERI_HCLK_DIV_MASK | > + PERI_ACLK_DIV_MASK, > + PERI_SEL_GPLL << PERI_SEL_PLL_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, > + GPLL_MODE_MASK | > + CPLL_MODE_MASK, > + GPLL_MODE_NORMAL << GPLL_MODE_SHIFT | > + CPLL_MODE_NORMAL << CPLL_MODE_SHIFT); > + > + rockchip_mmc_set_clk(cru, PERI_HCLK_HZ, HCLK_SDMMC, 16000000); Why is this 'rockchip_mmc_set_clk' necessary here? Will this not be requested by the MMC driver? > +} > +#endif > + > +static ulong rk3066_clk_get_rate(struct clk *clk) > +{ > + struct rk3066_clk_priv *priv = dev_get_priv(clk->dev); > + ulong new_rate, gclk_rate; > + > + gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL); > + switch (clk->id) { > + case 1 ... 4: > + new_rate = rkclk_pll_get_rate(priv->cru, clk->id); > + break; > + case HCLK_EMMC: > + case HCLK_SDMMC: > + case HCLK_SDIO: > + new_rate = rockchip_mmc_get_clk(priv->cru, PERI_HCLK_HZ, > + clk->id); > + break; > + case SCLK_SPI0: > + case SCLK_SPI1: > + new_rate = rockchip_spi_get_clk(priv->cru, PERI_PCLK_HZ, > + clk->id); > + break; > + case PCLK_I2C0: > + case PCLK_I2C1: > + case PCLK_I2C2: > + case PCLK_I2C3: > + case PCLK_I2C4: > + return gclk_rate; > + default: > + return -ENOENT; > + } > + > + return new_rate; > +} > + > +static ulong rk3066_clk_set_rate(struct clk *clk, ulong rate) > +{ > + struct rk3066_clk_priv *priv = dev_get_priv(clk->dev); > + struct rk3066_cru *cru = priv->cru; > + ulong new_rate; > + > + switch (clk->id) { > + case PLL_APLL: > + new_rate = rkclk_configure_cpu(priv->cru, priv->grf, rate, > + priv->has_bwadj); > + break; > + case CLK_DDR: > + new_rate = rkclk_configure_ddr(priv->cru, priv->grf, rate, > + priv->has_bwadj); > + break; > + case HCLK_EMMC: > + case HCLK_SDMMC: > + case HCLK_SDIO: > + new_rate = rockchip_mmc_set_clk(cru, PERI_HCLK_HZ, > + clk->id, rate); > + break; > + case SCLK_SPI0: > + case SCLK_SPI1: > + new_rate = rockchip_spi_set_clk(cru, PERI_PCLK_HZ, > + clk->id, rate); > + break; > + default: > + return -ENOENT; > + } > + > + return new_rate; > +} > + > +static struct clk_ops rk3066_clk_ops = { > + .get_rate = rk3066_clk_get_rate, > + .set_rate = rk3066_clk_set_rate, > +}; > + > +static int rk3066_clk_ofdata_to_platdata(struct udevice *dev) > +{ > +#if !CONFIG_IS_ENABLED(OF_PLATDATA) > + struct rk3066_clk_priv *priv = dev_get_priv(dev); > + > + priv->cru = (struct rk3066_cru *)devfdt_get_addr(dev); > +#endif > + > + return 0; > +} > + > +static int rk3066_clk_probe(struct udevice *dev) > +{ > + struct rk3066_clk_priv *priv = dev_get_priv(dev); > + > + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); > + if (IS_ERR(priv->grf)) > + return PTR_ERR(priv->grf); > + > +#ifdef CONFIG_TPL_BUILD > +#if CONFIG_IS_ENABLED(OF_PLATDATA) > + struct rk3066_clk_plat *plat = dev_get_platdata(dev); > + > + priv->cru = map_sysmem(plat->dtd.reg[0], plat->dtd.reg[1]); > +#endif > + > + rkclk_init(priv->cru, priv->grf, 1); > +#endif > + > + return 0; > +} > + > +static int rk3066_clk_bind(struct udevice *dev) > +{ > + int ret; > + > + /* The reset driver does not have a device node, so bind it here */ > + ret = device_bind_driver(gd->dm_root, "rk3066_sysreset", "reset", &dev); > + if (ret) > + debug("Warning: No rk3066 reset driver: ret=%d\n", ret); > + > + return 0; > +} > + > +static const struct udevice_id rk3066_clk_ids[] = { > + { .compatible = "rockchip,rk3066a-cru" }, > + { } > +}; > + > +U_BOOT_DRIVER(rockchip_rk3066a_cru) = { > + .name = "rockchip_rk3066a_cru", > + .id = UCLASS_CLK, > + .of_match = rk3066_clk_ids, > + .priv_auto_alloc_size = sizeof(struct rk3066_clk_priv), > + .platdata_auto_alloc_size = sizeof(struct rk3066_clk_plat), > + .ops = &rk3066_clk_ops, > + .bind = rk3066_clk_bind, > + .ofdata_to_platdata = rk3066_clk_ofdata_to_platdata, > + .probe = rk3066_clk_probe, > +}; >
diff --git a/arch/arm/include/asm/arch-rockchip/cru_rk3066.h b/arch/arm/include/asm/arch-rockchip/cru_rk3066.h new file mode 100644 index 0000000..c4a6951 --- /dev/null +++ b/arch/arm/include/asm/arch-rockchip/cru_rk3066.h @@ -0,0 +1,189 @@ +/* + * (C) Copyright 2016 Heiko Stuebner <heiko@sntech.de> + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef _ASM_ARCH_CRU_RK3066_H +#define _ASM_ARCH_CRU_RK3066_H + +#define OSC_HZ (24 * 1000 * 1000) + +#define APLL_HZ (1416 * 1000000) +#define APLL_SAFE_HZ (600 * 1000000) +#define GPLL_HZ (594 * 1000000) +#define CPLL_HZ (384 * 1000000) + +/* The SRAM is clocked off aclk_cpu, so we want to max it out for boot speed */ +#define CPU_ACLK_HZ 297000000 +#define CPU_HCLK_HZ 148500000 +#define CPU_PCLK_HZ 74250000 +#define CPU_H2P_HZ 74250000 + +#define PERI_ACLK_HZ 148500000 +#define PERI_HCLK_HZ 148500000 +#define PERI_PCLK_HZ 74250000 + +/* Private data for the clock driver - used by rockchip_get_cru() */ +struct rk3066_clk_priv { + struct rk3066_grf *grf; + struct rk3066_cru *cru; + ulong rate; + bool has_bwadj; +}; + +struct rk3066_cru { + struct rk3066_pll { + u32 con0; + u32 con1; + u32 con2; + u32 con3; + } pll[4]; + u32 cru_mode_con; + u32 cru_clksel_con[35]; + u32 cru_clkgate_con[10]; + u32 reserved1[2]; + u32 cru_glb_srst_fst_value; + u32 cru_glb_srst_snd_value; + u32 reserved2[2]; + u32 cru_softrst_con[9]; + u32 cru_misc_con; + u32 reserved3[2]; + u32 cru_glb_cnt_th; +}; +check_member(rk3066_cru, cru_glb_cnt_th, 0x0140); + +/* CRU_CLKSEL0_CON */ +enum { + /* a9_core_div: core = core_src / (a9_core_div + 1) */ + A9_CORE_DIV_SHIFT = 9, + A9_CORE_DIV_MASK = 0x1f << A9_CORE_DIV_SHIFT, + CORE_PLL_SHIFT = 8, + CORE_PLL_MASK = 1 << CORE_PLL_SHIFT, + CORE_PLL_SELECT_APLL = 0, + CORE_PLL_SELECT_GPLL, + + /* core peri div: core:core_peri = 2:1, 4:1, 8:1 or 16:1 */ + CORE_PERI_DIV_SHIFT = 6, + CORE_PERI_DIV_MASK = 3 << CORE_PERI_DIV_SHIFT, + + /* aclk_cpu pll selection */ + CPU_ACLK_PLL_SHIFT = 5, + CPU_ACLK_PLL_MASK = 1 << CPU_ACLK_PLL_SHIFT, + CPU_ACLK_PLL_SELECT_APLL = 0, + CPU_ACLK_PLL_SELECT_GPLL, + + /* a9_cpu_div: aclk_cpu = cpu_src / (a9_cpu_div + 1) */ + A9_CPU_DIV_SHIFT = 0, + A9_CPU_DIV_MASK = 0x1f << A9_CPU_DIV_SHIFT, +}; + +/* CRU_CLKSEL1_CON */ +enum { + /* ahb2apb_pclk_div: hclk_cpu:pclk_cpu = 1:1, 2:1 or 4:1 */ + AHB2APB_DIV_SHIFT = 14, + AHB2APB_DIV_MASK = 3 << AHB2APB_DIV_SHIFT, + + /* cpu_pclk_div: aclk_cpu:pclk_cpu = 1:1, 2:1, 4:1 or 8:1 */ + CPU_PCLK_DIV_SHIFT = 12, + CPU_PCLK_DIV_MASK = 3 << CPU_PCLK_DIV_SHIFT, + + /* cpu_hclk_div: aclk_cpu:hclk_cpu = 1:1, 2:1 or 4:1 */ + CPU_HCLK_DIV_SHIFT = 8, + CPU_HCLK_DIV_MASK = 3 << CPU_HCLK_DIV_SHIFT, + + /* core_aclk_div: cire:aclk_core = 1:1, 2:1, 3:1, 4:1 or 8:1 */ + CORE_ACLK_DIV_SHIFT = 3, + CORE_ACLK_DIV_MASK = 7 << CORE_ACLK_DIV_SHIFT, +}; + +/* CRU_CLKSEL10_CON */ +enum { + PERI_SEL_PLL_SHIFT = 15, + PERI_SEL_PLL_MASK = 1 << PERI_SEL_PLL_SHIFT, + PERI_SEL_CPLL = 0, + PERI_SEL_GPLL, + + /* peri pclk div: aclk_bus:pclk_bus = 1:1, 2:1, 4:1 or 8:1 */ + PERI_PCLK_DIV_SHIFT = 12, + PERI_PCLK_DIV_MASK = 3 << PERI_PCLK_DIV_SHIFT, + + /* peripheral bus hclk div:aclk_bus: hclk_bus = 1:1, 2:1 or 4:1 */ + PERI_HCLK_DIV_SHIFT = 8, + PERI_HCLK_DIV_MASK = 3 << PERI_HCLK_DIV_SHIFT, + + /* peri aclk div: aclk_peri = periph_src / (peri_aclk_div + 1) */ + PERI_ACLK_DIV_SHIFT = 0, + PERI_ACLK_DIV_MASK = 0x1f << PERI_ACLK_DIV_SHIFT, +}; +/* CRU_CLKSEL11_CON */ +enum { + MMC0_DIV_SHIFT = 0, + MMC0_DIV_MASK = 0x3f << MMC0_DIV_SHIFT, +}; + +/* CRU_CLKSEL12_CON */ +enum { + UART_PLL_SHIFT = 15, + UART_PLL_MASK = 1 << UART_PLL_SHIFT, + UART_PLL_SELECT_GENERAL = 0, + UART_PLL_SELECT_CODEC, + + EMMC_DIV_SHIFT = 8, + EMMC_DIV_MASK = 0x3f << EMMC_DIV_SHIFT, + + SDIO_DIV_SHIFT = 0, + SDIO_DIV_MASK = 0x3f << SDIO_DIV_SHIFT, +}; + +/* CRU_CLKSEL25_CON */ +enum { + SPI1_DIV_SHIFT = 8, + SPI1_DIV_MASK = 0x7f << SPI1_DIV_SHIFT, + + SPI0_DIV_SHIFT = 0, + SPI0_DIV_MASK = 0x7f << SPI0_DIV_SHIFT, +}; + +/* CRU_MODE_CON */ +enum { + GPLL_MODE_SHIFT = 12, + GPLL_MODE_MASK = 3 << GPLL_MODE_SHIFT, + GPLL_MODE_SLOW = 0, + GPLL_MODE_NORMAL, + GPLL_MODE_DEEP, + + CPLL_MODE_SHIFT = 8, + CPLL_MODE_MASK = 3 << CPLL_MODE_SHIFT, + CPLL_MODE_SLOW = 0, + CPLL_MODE_NORMAL, + CPLL_MODE_DEEP, + + DPLL_MODE_SHIFT = 4, + DPLL_MODE_MASK = 3 << DPLL_MODE_SHIFT, + DPLL_MODE_SLOW = 0, + DPLL_MODE_NORMAL, + DPLL_MODE_DEEP, + + APLL_MODE_SHIFT = 0, + APLL_MODE_MASK = 3 << APLL_MODE_SHIFT, + APLL_MODE_SLOW = 0, + APLL_MODE_NORMAL, + APLL_MODE_DEEP, +}; + +/* CRU_APLL_CON0 */ +enum { + CLKR_SHIFT = 8, + CLKR_MASK = 0x3f << CLKR_SHIFT, + + CLKOD_SHIFT = 0, + CLKOD_MASK = 0x3f << CLKOD_SHIFT, +}; + +/* CRU_APLL_CON1 */ +enum { + CLKF_SHIFT = 0, + CLKF_MASK = 0x1fff << CLKF_SHIFT, +}; + +#endif diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index c50aff2..167b08e 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -5,6 +5,7 @@ # obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o +obj-$(CONFIG_ROCKCHIP_RK3066) += clk_rk3066.o obj-$(CONFIG_ROCKCHIP_RK3188) += clk_rk3188.o obj-$(CONFIG_ROCKCHIP_RK322X) += clk_rk322x.o obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o diff --git a/drivers/clk/rockchip/clk_rk3066.c b/drivers/clk/rockchip/clk_rk3066.c new file mode 100644 index 0000000..bcf65e0 --- /dev/null +++ b/drivers/clk/rockchip/clk_rk3066.c @@ -0,0 +1,587 @@ +/* + * (C) Copyright 2015 Google, Inc + * (C) Copyright 2016 Heiko Stuebner <heiko@sntech.de> + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <common.h> +#include <clk-uclass.h> +#include <dm.h> +#include <dt-structs.h> +#include <errno.h> +#include <mapmem.h> +#include <syscon.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/cru_rk3066.h> +#include <asm/arch/grf_rk3066.h> +#include <asm/arch/hardware.h> +#include <dt-bindings/clock/rk3066a-cru.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/uclass-internal.h> +#include <linux/log2.h> + +DECLARE_GLOBAL_DATA_PTR; + +enum rk3066_clk_type { + RK3066_CRU, + RK3066A_CRU, +}; + +struct rk3066_clk_plat { +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_rockchip_rk3066a_cru dtd; +#endif +}; + +struct pll_div { + u32 nr; + u32 nf; + u32 no; +}; + +enum { + VCO_MAX_HZ = 1416U * 1000000, + VCO_MIN_HZ = 300 * 1000000, + OUTPUT_MAX_HZ = 1416U * 1000000, + OUTPUT_MIN_HZ = 30 * 1000000, + FREF_MAX_HZ = 1416U * 1000000, + FREF_MIN_HZ = 30 * 1000, +}; + +enum { + /* PLL CON0 */ + PLL_OD_MASK = 0x0f, + + /* PLL CON1 */ + PLL_NF_MASK = 0x1fff, + + /* PLL CON2 */ + PLL_BWADJ_MASK = 0x0fff, + + /* PLL CON3 */ + PLL_RESET_SHIFT = 5, + + /* GRF_SOC_STATUS0 */ + SOCSTS_DPLL_LOCK = 1 << 4, + SOCSTS_APLL_LOCK = 1 << 5, + SOCSTS_CPLL_LOCK = 1 << 6, + SOCSTS_GPLL_LOCK = 1 << 7, +}; + +#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, _nr, _no) {\ + .nr = _nr, .nf = (u32)((u64)hz * _nr * _no / OSC_HZ), .no = _no};\ + _Static_assert(((u64)hz * _nr * _no / OSC_HZ) * OSC_HZ /\ + (_nr * _no) == hz, #hz "Hz cannot be hit with PLL "\ + "divisors on line " __stringify(__LINE__)); + +/* Keep divisors as low as possible to reduce jitter and power usage */ +#ifdef CONFIG_TPL_BUILD +static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2); +static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 1, 2); +#endif + +static int rkclk_set_pll(struct rk3066_cru *cru, enum rk_clk_id clk_id, + const struct pll_div *div, bool has_bwadj) +{ + int pll_id = rk_pll_id(clk_id); + struct rk3066_pll *pll = &cru->pll[pll_id]; + /* All PLLs have same VCO and output frequency range restrictions. */ + uint vco_hz = OSC_HZ / 1000 * div->nf / div->nr * 1000; + uint output_hz = vco_hz / div->no; + + debug("PLL at %x: nf=%d, nr=%d, no=%d, vco=%u Hz, output=%u Hz\n", + (uint)pll, div->nf, div->nr, div->no, 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 && + (div->no == 1 || !(div->no % 2))); + + /* enter reset */ + rk_setreg(&pll->con3, 1 << PLL_RESET_SHIFT); + + rk_clrsetreg(&pll->con0, + CLKR_MASK | PLL_OD_MASK, + ((div->nr - 1) << CLKR_SHIFT) | (div->no - 1)); + rk_clrsetreg(&pll->con1, CLKF_MASK, div->nf - 1); + + if (has_bwadj) + rk_clrsetreg(&pll->con2, PLL_BWADJ_MASK, (div->nf >> 1) - 1); + + udelay(10); + + /* return from reset */ + rk_clrreg(&pll->con3, 1 << PLL_RESET_SHIFT); + + return 0; +} + +static int rkclk_configure_ddr(struct rk3066_cru *cru, struct rk3066_grf *grf, + unsigned int hz, bool has_bwadj) +{ + static const struct pll_div dpll_cfg[] = { + {.nf = 25, .nr = 2, .no = 1}, + {.nf = 400, .nr = 9, .no = 2}, + {.nf = 500, .nr = 9, .no = 2}, + {.nf = 100, .nr = 3, .no = 1}, + }; + int cfg; + + switch (hz) { + case 300000000: + cfg = 0; + break; + case 533000000: /* actually 533.3P MHz */ + cfg = 1; + break; + case 666000000: /* actually 666.6P MHz */ + cfg = 2; + break; + case 800000000: + cfg = 3; + break; + default: + debug("Unsupported SDRAM frequency"); + return -EINVAL; + } + + /* pll enter slow-mode */ + rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK, + DPLL_MODE_SLOW << DPLL_MODE_SHIFT); + + rkclk_set_pll(cru, CLK_DDR, &dpll_cfg[cfg], has_bwadj); + + /* wait for pll lock */ + while (!(readl(&grf->soc_status0) & SOCSTS_DPLL_LOCK)) + udelay(1); + + /* PLL enter normal-mode */ + rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK, + DPLL_MODE_NORMAL << DPLL_MODE_SHIFT); + + return 0; +} + +static int rkclk_configure_cpu(struct rk3066_cru *cru, struct rk3066_grf *grf, + unsigned int hz, bool has_bwadj) +{ + static const struct pll_div apll_cfg[] = { + {.nf = 50, .nr = 1, .no = 2}, + {.nf = 59, .nr = 1, .no = 1}, + }; + int div_core_peri, div_aclk_core, cfg; + + /* + * We support two possible frequencies, the safe 600MHz + * which will work with default pmic settings and will + * be set to get away from the 24MHz default and + * the maximum of 1.416Ghz, which boards can set if they + * were able to get pmic support for it. + */ + switch (hz) { + case APLL_SAFE_HZ: + cfg = 0; + div_core_peri = 1; + div_aclk_core = 3; + break; + case APLL_HZ: + cfg = 1; + div_core_peri = 2; + div_aclk_core = 3; + break; + default: + debug("Unsupported ARMCLK frequency"); + return -EINVAL; + } + + /* pll enter slow-mode */ + rk_clrsetreg(&cru->cru_mode_con, APLL_MODE_MASK, + APLL_MODE_SLOW << APLL_MODE_SHIFT); + + rkclk_set_pll(cru, CLK_ARM, &apll_cfg[cfg], has_bwadj); + + /* waiting for pll lock */ + while (!(readl(&grf->soc_status0) & SOCSTS_APLL_LOCK)) + udelay(1); + + /* Set divider for peripherals attached to the cpu core. */ + rk_clrsetreg(&cru->cru_clksel_con[0], + CORE_PERI_DIV_MASK, + div_core_peri << CORE_PERI_DIV_SHIFT); + + /* set up dependent divisor for aclk_core */ + rk_clrsetreg(&cru->cru_clksel_con[1], + CORE_ACLK_DIV_MASK, + div_aclk_core << CORE_ACLK_DIV_SHIFT); + + /* PLL enter normal-mode */ + rk_clrsetreg(&cru->cru_mode_con, APLL_MODE_MASK, + APLL_MODE_NORMAL << APLL_MODE_SHIFT); + + return hz; +} + +/* Get pll rate by id */ +static uint32_t rkclk_pll_get_rate(struct rk3066_cru *cru, + enum rk_clk_id clk_id) +{ + uint32_t nr, no, nf; + uint32_t con; + int pll_id = rk_pll_id(clk_id); + struct rk3066_pll *pll = &cru->pll[pll_id]; + static u8 clk_shift[CLK_COUNT] = { + 0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, CPLL_MODE_SHIFT, + GPLL_MODE_SHIFT + }; + uint shift; + + con = readl(&cru->cru_mode_con); + shift = clk_shift[clk_id]; + switch ((con >> shift) & APLL_MODE_MASK >> APLL_MODE_SHIFT) { + case APLL_MODE_SLOW: + return OSC_HZ; + case APLL_MODE_NORMAL: + /* normal mode */ + con = readl(&pll->con0); + no = ((con >> CLKOD_SHIFT) & (CLKOD_MASK >> CLKOD_SHIFT)) + 1; + nr = ((con >> CLKR_SHIFT) & (CLKR_MASK >> CLKR_SHIFT)) + 1; + con = readl(&pll->con1); + nf = ((con >> CLKF_SHIFT) & (CLKF_MASK >> CLKF_SHIFT)) + 1; + + return (24 * nf / (nr * no)) * 1000000; + case APLL_MODE_DEEP: + default: + return 32768; + } +} + +static ulong rockchip_mmc_get_clk(struct rk3066_cru *cru, uint gclk_rate, + int periph) +{ + uint div; + u32 con; + + switch (periph) { + case HCLK_EMMC: + con = readl(&cru->cru_clksel_con[12]); + div = (con >> EMMC_DIV_SHIFT) & EMMC_DIV_MASK >> EMMC_DIV_SHIFT; + break; + case HCLK_SDMMC: + con = readl(&cru->cru_clksel_con[11]); + div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK >> MMC0_DIV_SHIFT; + break; + case HCLK_SDIO: + con = readl(&cru->cru_clksel_con[12]); + div = (con >> SDIO_DIV_SHIFT) & SDIO_DIV_MASK >> SDIO_DIV_SHIFT; + break; + default: + return -EINVAL; + } + + return DIV_TO_RATE(gclk_rate, div); +} + +static ulong rockchip_mmc_set_clk(struct rk3066_cru *cru, uint gclk_rate, + int periph, uint freq) +{ + int src_clk_div; + + debug("%s: gclk_rate=%u\n", __func__, gclk_rate); + src_clk_div = RATE_TO_DIV(gclk_rate, freq); + if (src_clk_div > 0x3f) + src_clk_div = 0x3f; + + switch (periph) { + case HCLK_EMMC: + rk_clrsetreg(&cru->cru_clksel_con[12], + EMMC_DIV_MASK, + src_clk_div << EMMC_DIV_SHIFT); + break; + case HCLK_SDMMC: + rk_clrsetreg(&cru->cru_clksel_con[11], + MMC0_DIV_MASK, + src_clk_div << MMC0_DIV_SHIFT); + break; + case HCLK_SDIO: + rk_clrsetreg(&cru->cru_clksel_con[12], + SDIO_DIV_MASK, + src_clk_div << SDIO_DIV_SHIFT); + break; + default: + return -EINVAL; + } + + return rockchip_mmc_get_clk(cru, gclk_rate, periph); +} + +static ulong rockchip_spi_get_clk(struct rk3066_cru *cru, uint gclk_rate, + int periph) +{ + uint div; + u32 con; + + switch (periph) { + case SCLK_SPI0: + con = readl(&cru->cru_clksel_con[25]); + div = (con >> SPI0_DIV_SHIFT) & SPI0_DIV_MASK >> SPI0_DIV_SHIFT; + break; + case SCLK_SPI1: + con = readl(&cru->cru_clksel_con[25]); + div = (con >> SPI1_DIV_SHIFT) & SPI1_DIV_MASK >> SPI1_DIV_SHIFT; + break; + default: + return -EINVAL; + } + + return DIV_TO_RATE(gclk_rate, div); +} + +static ulong rockchip_spi_set_clk(struct rk3066_cru *cru, uint gclk_rate, + int periph, uint freq) +{ + int src_clk_div = RATE_TO_DIV(gclk_rate, freq); + + switch (periph) { + case SCLK_SPI0: + assert(src_clk_div <= SPI0_DIV_MASK >> SPI0_DIV_SHIFT); + rk_clrsetreg(&cru->cru_clksel_con[25], + SPI0_DIV_MASK, + src_clk_div << SPI0_DIV_SHIFT); + break; + case SCLK_SPI1: + assert(src_clk_div <= SPI1_DIV_MASK >> SPI1_DIV_SHIFT); + rk_clrsetreg(&cru->cru_clksel_con[25], + SPI1_DIV_MASK, + src_clk_div << SPI1_DIV_SHIFT); + break; + default: + return -EINVAL; + } + + return rockchip_spi_get_clk(cru, gclk_rate, periph); +} +#ifdef CONFIG_TPL_BUILD +static void rkclk_init(struct rk3066_cru *cru, struct rk3066_grf *grf, + bool has_bwadj) +{ + u32 aclk_div, hclk_div, pclk_div, h2p_div; + + /* pll enter slow-mode */ + rk_clrsetreg(&cru->cru_mode_con, + GPLL_MODE_MASK | + CPLL_MODE_MASK, + GPLL_MODE_SLOW << GPLL_MODE_SHIFT | + CPLL_MODE_SLOW << CPLL_MODE_SHIFT); + + /* init pll */ + rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg, has_bwadj); + rkclk_set_pll(cru, CLK_CODEC, &cpll_init_cfg, has_bwadj); + + /* waiting for pll lock */ + while ((readl(&grf->soc_status0) & + (SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) != + (SOCSTS_CPLL_LOCK | SOCSTS_GPLL_LOCK)) + udelay(1); + + /* + * cpu clock pll source selection and + * reparent aclk_cpu_pre from apll to gpll + * set up dependent divisors for PCLK/HCLK and ACLK clocks. + */ + aclk_div = RATE_TO_DIV(GPLL_HZ, CPU_ACLK_HZ); + assert((aclk_div + 1) * CPU_ACLK_HZ == GPLL_HZ && aclk_div < 0x1f); + + rk_clrsetreg(&cru->cru_clksel_con[0], + CPU_ACLK_PLL_MASK | + A9_CPU_DIV_MASK, + CPU_ACLK_PLL_SELECT_GPLL << CPU_ACLK_PLL_SHIFT | + aclk_div << A9_CPU_DIV_SHIFT); + + hclk_div = ilog2(CPU_ACLK_HZ / CPU_HCLK_HZ); + assert((1 << hclk_div) * CPU_HCLK_HZ == CPU_ACLK_HZ && hclk_div < 0x3); + pclk_div = ilog2(CPU_ACLK_HZ / CPU_PCLK_HZ); + assert((1 << pclk_div) * CPU_PCLK_HZ == CPU_ACLK_HZ && pclk_div < 0x4); + h2p_div = ilog2(CPU_HCLK_HZ / CPU_H2P_HZ); + assert((1 << h2p_div) * CPU_H2P_HZ == CPU_HCLK_HZ && pclk_div < 0x3); + + rk_clrsetreg(&cru->cru_clksel_con[1], + AHB2APB_DIV_MASK | + CPU_PCLK_DIV_MASK | + CPU_HCLK_DIV_MASK, + h2p_div << AHB2APB_DIV_SHIFT | + pclk_div << CPU_PCLK_DIV_SHIFT | + hclk_div << CPU_HCLK_DIV_SHIFT); + + /* + * peri clock pll source selection and + * 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); + + hclk_div = ilog2(PERI_ACLK_HZ / PERI_HCLK_HZ); + assert((1 << hclk_div) * PERI_HCLK_HZ == + PERI_ACLK_HZ && (hclk_div < 0x4)); + + pclk_div = ilog2(PERI_ACLK_HZ / PERI_PCLK_HZ); + assert((1 << pclk_div) * PERI_PCLK_HZ == + PERI_ACLK_HZ && (pclk_div < 0x4)); + + rk_clrsetreg(&cru->cru_clksel_con[10], + PERI_PCLK_DIV_MASK | + PERI_HCLK_DIV_MASK | + PERI_ACLK_DIV_MASK, + PERI_SEL_GPLL << PERI_SEL_PLL_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, + GPLL_MODE_MASK | + CPLL_MODE_MASK, + GPLL_MODE_NORMAL << GPLL_MODE_SHIFT | + CPLL_MODE_NORMAL << CPLL_MODE_SHIFT); + + rockchip_mmc_set_clk(cru, PERI_HCLK_HZ, HCLK_SDMMC, 16000000); +} +#endif + +static ulong rk3066_clk_get_rate(struct clk *clk) +{ + struct rk3066_clk_priv *priv = dev_get_priv(clk->dev); + ulong new_rate, gclk_rate; + + gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL); + switch (clk->id) { + case 1 ... 4: + new_rate = rkclk_pll_get_rate(priv->cru, clk->id); + break; + case HCLK_EMMC: + case HCLK_SDMMC: + case HCLK_SDIO: + new_rate = rockchip_mmc_get_clk(priv->cru, PERI_HCLK_HZ, + clk->id); + break; + case SCLK_SPI0: + case SCLK_SPI1: + new_rate = rockchip_spi_get_clk(priv->cru, PERI_PCLK_HZ, + clk->id); + break; + case PCLK_I2C0: + case PCLK_I2C1: + case PCLK_I2C2: + case PCLK_I2C3: + case PCLK_I2C4: + return gclk_rate; + default: + return -ENOENT; + } + + return new_rate; +} + +static ulong rk3066_clk_set_rate(struct clk *clk, ulong rate) +{ + struct rk3066_clk_priv *priv = dev_get_priv(clk->dev); + struct rk3066_cru *cru = priv->cru; + ulong new_rate; + + switch (clk->id) { + case PLL_APLL: + new_rate = rkclk_configure_cpu(priv->cru, priv->grf, rate, + priv->has_bwadj); + break; + case CLK_DDR: + new_rate = rkclk_configure_ddr(priv->cru, priv->grf, rate, + priv->has_bwadj); + break; + case HCLK_EMMC: + case HCLK_SDMMC: + case HCLK_SDIO: + new_rate = rockchip_mmc_set_clk(cru, PERI_HCLK_HZ, + clk->id, rate); + break; + case SCLK_SPI0: + case SCLK_SPI1: + new_rate = rockchip_spi_set_clk(cru, PERI_PCLK_HZ, + clk->id, rate); + break; + default: + return -ENOENT; + } + + return new_rate; +} + +static struct clk_ops rk3066_clk_ops = { + .get_rate = rk3066_clk_get_rate, + .set_rate = rk3066_clk_set_rate, +}; + +static int rk3066_clk_ofdata_to_platdata(struct udevice *dev) +{ +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + struct rk3066_clk_priv *priv = dev_get_priv(dev); + + priv->cru = (struct rk3066_cru *)devfdt_get_addr(dev); +#endif + + return 0; +} + +static int rk3066_clk_probe(struct udevice *dev) +{ + struct rk3066_clk_priv *priv = dev_get_priv(dev); + + priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF); + if (IS_ERR(priv->grf)) + return PTR_ERR(priv->grf); + +#ifdef CONFIG_TPL_BUILD +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct rk3066_clk_plat *plat = dev_get_platdata(dev); + + priv->cru = map_sysmem(plat->dtd.reg[0], plat->dtd.reg[1]); +#endif + + rkclk_init(priv->cru, priv->grf, 1); +#endif + + return 0; +} + +static int rk3066_clk_bind(struct udevice *dev) +{ + int ret; + + /* The reset driver does not have a device node, so bind it here */ + ret = device_bind_driver(gd->dm_root, "rk3066_sysreset", "reset", &dev); + if (ret) + debug("Warning: No rk3066 reset driver: ret=%d\n", ret); + + return 0; +} + +static const struct udevice_id rk3066_clk_ids[] = { + { .compatible = "rockchip,rk3066a-cru" }, + { } +}; + +U_BOOT_DRIVER(rockchip_rk3066a_cru) = { + .name = "rockchip_rk3066a_cru", + .id = UCLASS_CLK, + .of_match = rk3066_clk_ids, + .priv_auto_alloc_size = sizeof(struct rk3066_clk_priv), + .platdata_auto_alloc_size = sizeof(struct rk3066_clk_plat), + .ops = &rk3066_clk_ops, + .bind = rk3066_clk_bind, + .ofdata_to_platdata = rk3066_clk_ofdata_to_platdata, + .probe = rk3066_clk_probe, +};