From bbda2ed5840c67758fb172fbcdd6398f6ab5fe25 Mon Sep 17 00:00:00 2001 From: Elaine Zhang Date: Fri, 25 Oct 2019 09:42:17 +0800 Subject: [PATCH] rockchip: clk: pll: add common pll setting funcs Common PLL setup function, compatible with different SOC. Mainly for the subsequent new SOC use. Signed-off-by: Elaine Zhang Reviewed-by: Kever Yang --- arch/arm/include/asm/arch-rockchip/clock.h | 76 +++++ drivers/clk/rockchip/Makefile | 1 + drivers/clk/rockchip/clk_pll.c | 360 +++++++++++++++++++++ 3 files changed, 437 insertions(+) create mode 100644 drivers/clk/rockchip/clk_pll.c diff --git a/arch/arm/include/asm/arch-rockchip/clock.h b/arch/arm/include/asm/arch-rockchip/clock.h index 1d5b3a07d0..8f7fc86a9e 100644 --- a/arch/arm/include/asm/arch-rockchip/clock.h +++ b/arch/arm/include/asm/arch-rockchip/clock.h @@ -9,6 +9,7 @@ /* define pll mode */ #define RKCLK_PLL_MODE_SLOW 0 #define RKCLK_PLL_MODE_NORMAL 1 +#define RKCLK_PLL_MODE_DEEP 2 enum { ROCKCHIP_SYSCON_NOC, @@ -33,6 +34,81 @@ enum rk_clk_id { CLK_COUNT, }; +#define PLL(_type, _id, _con, _mode, _mshift, \ + _lshift, _pflags, _rtable) \ + { \ + .id = _id, \ + .type = _type, \ + .con_offset = _con, \ + .mode_offset = _mode, \ + .mode_shift = _mshift, \ + .lock_shift = _lshift, \ + .pll_flags = _pflags, \ + .rate_table = _rtable, \ + } + +#define RK3036_PLL_RATE(_rate, _refdiv, _fbdiv, _postdiv1, \ + _postdiv2, _dsmpd, _frac) \ +{ \ + .rate = _rate##U, \ + .fbdiv = _fbdiv, \ + .postdiv1 = _postdiv1, \ + .refdiv = _refdiv, \ + .postdiv2 = _postdiv2, \ + .dsmpd = _dsmpd, \ + .frac = _frac, \ +} + +struct rockchip_pll_rate_table { + unsigned long rate; + unsigned int nr; + unsigned int nf; + unsigned int no; + unsigned int nb; + /* for RK3036/RK3399 */ + unsigned int fbdiv; + unsigned int postdiv1; + unsigned int refdiv; + unsigned int postdiv2; + unsigned int dsmpd; + unsigned int frac; +}; + +enum rockchip_pll_type { + pll_rk3036, + pll_rk3066, + pll_rk3328, + pll_rk3366, + pll_rk3399, +}; + +struct rockchip_pll_clock { + unsigned int id; + unsigned int con_offset; + unsigned int mode_offset; + unsigned int mode_shift; + unsigned int lock_shift; + enum rockchip_pll_type type; + unsigned int pll_flags; + struct rockchip_pll_rate_table *rate_table; + unsigned int mode_mask; +}; + +struct rockchip_cpu_rate_table { + unsigned long rate; + unsigned int aclk_div; + unsigned int pclk_div; +}; + +int rockchip_pll_set_rate(struct rockchip_pll_clock *pll, + void __iomem *base, ulong clk_id, + ulong drate); +ulong rockchip_pll_get_rate(struct rockchip_pll_clock *pll, + void __iomem *base, ulong clk_id); +const struct rockchip_cpu_rate_table * +rockchip_get_cpu_settings(struct rockchip_cpu_rate_table *cpu_table, + ulong rate); + static inline int rk_pll_id(enum rk_clk_id clk_id) { return clk_id - 1; diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index f9134fd51f..8035b26136 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -3,6 +3,7 @@ # Copyright (c) 2017 Rockchip Electronics Co., Ltd # +obj-y += clk_pll.o obj-$(CONFIG_ROCKCHIP_PX30) += clk_px30.o obj-$(CONFIG_ROCKCHIP_RK3036) += clk_rk3036.o obj-$(CONFIG_ROCKCHIP_RK3128) += clk_rk3128.o diff --git a/drivers/clk/rockchip/clk_pll.c b/drivers/clk/rockchip/clk_pll.c new file mode 100644 index 0000000000..c4b45314ec --- /dev/null +++ b/drivers/clk/rockchip/clk_pll.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) Copyright 2018-2019 Rockchip Electronics Co., Ltd + */ + #include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct rockchip_pll_rate_table rockchip_auto_table; + +#define PLL_MODE_MASK 0x3 +#define PLL_RK3328_MODE_MASK 0x1 + +#define RK3036_PLLCON0_FBDIV_MASK 0xfff +#define RK3036_PLLCON0_FBDIV_SHIFT 0 +#define RK3036_PLLCON0_POSTDIV1_MASK 0x7 << 12 +#define RK3036_PLLCON0_POSTDIV1_SHIFT 12 +#define RK3036_PLLCON1_REFDIV_MASK 0x3f +#define RK3036_PLLCON1_REFDIV_SHIFT 0 +#define RK3036_PLLCON1_POSTDIV2_MASK 0x7 << 6 +#define RK3036_PLLCON1_POSTDIV2_SHIFT 6 +#define RK3036_PLLCON1_DSMPD_MASK 0x1 << 12 +#define RK3036_PLLCON1_DSMPD_SHIFT 12 +#define RK3036_PLLCON2_FRAC_MASK 0xffffff +#define RK3036_PLLCON2_FRAC_SHIFT 0 +#define RK3036_PLLCON1_PWRDOWN_SHIT 13 + +#define MHZ 1000000 +#define KHZ 1000 +enum { + OSC_HZ = 24 * 1000000, + VCO_MAX_HZ = 3200U * 1000000, + VCO_MIN_HZ = 800 * 1000000, + OUTPUT_MAX_HZ = 3200U * 1000000, + OUTPUT_MIN_HZ = 24 * 1000000, +}; + +#define MIN_FOUTVCO_FREQ (800 * MHZ) +#define MAX_FOUTVCO_FREQ (2000 * MHZ) + +int gcd(int m, int n) +{ + int t; + + while (m > 0) { + if (n > m) { + t = m; + m = n; + n = t; + } /* swap */ + m -= n; + } + return n; +} + +/* + * How to calculate the PLL(from TRM V0.3 Part 1 Page 63): + * Formulas also embedded within the Fractional PLL Verilog model: + * If DSMPD = 1 (DSM is disabled, "integer mode") + * FOUTVCO = FREF / REFDIV * FBDIV + * FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2 + * Where: + * FOUTVCO = Fractional PLL non-divided output frequency + * FOUTPOSTDIV = Fractional PLL divided output frequency + * (output of second post divider) + * FREF = Fractional PLL input reference frequency, (the OSC_HZ 24MHz input) + * REFDIV = Fractional PLL input reference clock divider + * FBDIV = Integer value programmed into feedback divide + * + */ + +static int rockchip_pll_clk_set_postdiv(ulong fout_hz, + u32 *postdiv1, + u32 *postdiv2, + u32 *foutvco) +{ + ulong freq; + + if (fout_hz < MIN_FOUTVCO_FREQ) { + for (*postdiv1 = 1; *postdiv1 <= 7; (*postdiv1)++) { + for (*postdiv2 = 1; *postdiv2 <= 7; (*postdiv2)++) { + freq = fout_hz * (*postdiv1) * (*postdiv2); + if (freq >= MIN_FOUTVCO_FREQ && + freq <= MAX_FOUTVCO_FREQ) { + *foutvco = freq; + return 0; + } + } + } + printf("Can't FIND postdiv1/2 to make fout=%lu in 800~2000M.\n", + fout_hz); + } else { + *postdiv1 = 1; + *postdiv2 = 1; + } + return 0; +} + +static struct rockchip_pll_rate_table * +rockchip_pll_clk_set_by_auto(ulong fin_hz, + ulong fout_hz) +{ + struct rockchip_pll_rate_table *rate_table = &rockchip_auto_table; + /* FIXME set postdiv1/2 always 1*/ + u32 foutvco = fout_hz; + ulong fin_64, frac_64; + u32 f_frac, postdiv1, postdiv2; + ulong clk_gcd = 0; + + if (fin_hz == 0 || fout_hz == 0 || fout_hz == fin_hz) + return NULL; + + rockchip_pll_clk_set_postdiv(fout_hz, &postdiv1, &postdiv2, &foutvco); + rate_table->postdiv1 = postdiv1; + rate_table->postdiv2 = postdiv2; + rate_table->dsmpd = 1; + + if (fin_hz / MHZ * MHZ == fin_hz && fout_hz / MHZ * MHZ == fout_hz) { + fin_hz /= MHZ; + foutvco /= MHZ; + clk_gcd = gcd(fin_hz, foutvco); + rate_table->refdiv = fin_hz / clk_gcd; + rate_table->fbdiv = foutvco / clk_gcd; + + rate_table->frac = 0; + + debug("fin = %ld, fout = %ld, clk_gcd = %ld,\n", + fin_hz, fout_hz, clk_gcd); + debug("refdiv= %d,fbdiv= %d,postdiv1= %d,postdiv2= %d\n", + rate_table->refdiv, + rate_table->fbdiv, rate_table->postdiv1, + rate_table->postdiv2); + } else { + debug("frac div,fin_hz = %ld,fout_hz = %ld\n", + fin_hz, fout_hz); + debug("frac get postdiv1 = %d, postdiv2 = %d, foutvco = %d\n", + rate_table->postdiv1, rate_table->postdiv2, foutvco); + clk_gcd = gcd(fin_hz / MHZ, foutvco / MHZ); + rate_table->refdiv = fin_hz / MHZ / clk_gcd; + rate_table->fbdiv = foutvco / MHZ / clk_gcd; + debug("frac get refdiv = %d, fbdiv = %d\n", + rate_table->refdiv, rate_table->fbdiv); + + rate_table->frac = 0; + + f_frac = (foutvco % MHZ); + fin_64 = fin_hz; + fin_64 = fin_64 / rate_table->refdiv; + frac_64 = f_frac << 24; + frac_64 = frac_64 / fin_64; + rate_table->frac = frac_64; + if (rate_table->frac > 0) + rate_table->dsmpd = 0; + debug("frac = %x\n", rate_table->frac); + } + return rate_table; +} + +static const struct rockchip_pll_rate_table * +rockchip_get_pll_settings(struct rockchip_pll_clock *pll, ulong rate) +{ + struct rockchip_pll_rate_table *rate_table = pll->rate_table; + + while (rate_table->rate) { + if (rate_table->rate == rate) + break; + rate_table++; + } + if (rate_table->rate != rate) + return rockchip_pll_clk_set_by_auto(24 * MHZ, rate); + else + return rate_table; +} + +static int rk3036_pll_set_rate(struct rockchip_pll_clock *pll, + void __iomem *base, ulong pll_id, + ulong drate) +{ + const struct rockchip_pll_rate_table *rate; + + rate = rockchip_get_pll_settings(pll, drate); + if (!rate) { + printf("%s unsupport rate\n", __func__); + return -EINVAL; + } + + debug("%s: rate settings for %lu fbdiv: %d, postdiv1: %d, refdiv: %d\n", + __func__, rate->rate, rate->fbdiv, rate->postdiv1, rate->refdiv); + debug("%s: rate settings for %lu postdiv2: %d, dsmpd: %d, frac: %d\n", + __func__, rate->rate, rate->postdiv2, rate->dsmpd, rate->frac); + + /* + * When power on or changing PLL setting, + * we must force PLL into slow mode to ensure output stable clock. + */ + rk_clrsetreg(base + pll->mode_offset, + pll->mode_mask << pll->mode_shift, + RKCLK_PLL_MODE_SLOW << pll->mode_shift); + + /* Power down */ + rk_setreg(base + pll->con_offset + 0x4, + 1 << RK3036_PLLCON1_PWRDOWN_SHIT); + + rk_clrsetreg(base + pll->con_offset, + (RK3036_PLLCON0_POSTDIV1_MASK | + RK3036_PLLCON0_FBDIV_MASK), + (rate->postdiv1 << RK3036_PLLCON0_POSTDIV1_SHIFT) | + rate->fbdiv); + rk_clrsetreg(base + pll->con_offset + 0x4, + (RK3036_PLLCON1_POSTDIV2_MASK | + RK3036_PLLCON1_REFDIV_MASK), + (rate->postdiv2 << RK3036_PLLCON1_POSTDIV2_SHIFT | + rate->refdiv << RK3036_PLLCON1_REFDIV_SHIFT)); + if (!rate->dsmpd) { + rk_clrsetreg(base + pll->con_offset + 0x4, + RK3036_PLLCON1_DSMPD_MASK, + rate->dsmpd << RK3036_PLLCON1_DSMPD_SHIFT); + writel((readl(base + pll->con_offset + 0x8) & + (~RK3036_PLLCON2_FRAC_MASK)) | + (rate->frac << RK3036_PLLCON2_FRAC_SHIFT), + base + pll->con_offset + 0x8); + } + + /* Power Up */ + rk_clrreg(base + pll->con_offset + 0x4, + 1 << RK3036_PLLCON1_PWRDOWN_SHIT); + + /* waiting for pll lock */ + while (!(readl(base + pll->con_offset + 0x4) & (1 << pll->lock_shift))) + udelay(1); + + rk_clrsetreg(base + pll->mode_offset, pll->mode_mask << pll->mode_shift, + RKCLK_PLL_MODE_NORMAL << pll->mode_shift); + debug("PLL at %p: con0=%x con1= %x con2= %x mode= %x\n", + pll, readl(base + pll->con_offset), + readl(base + pll->con_offset + 0x4), + readl(base + pll->con_offset + 0x8), + readl(base + pll->mode_offset)); + + return 0; +} + +static ulong rk3036_pll_get_rate(struct rockchip_pll_clock *pll, + void __iomem *base, ulong pll_id) +{ + u32 refdiv, fbdiv, postdiv1, postdiv2, dsmpd, frac; + u32 con = 0, shift, mask; + ulong rate; + + con = readl(base + pll->mode_offset); + shift = pll->mode_shift; + mask = pll->mode_mask << shift; + + switch ((con & mask) >> shift) { + case RKCLK_PLL_MODE_SLOW: + return OSC_HZ; + case RKCLK_PLL_MODE_NORMAL: + /* normal mode */ + con = readl(base + pll->con_offset); + postdiv1 = (con & RK3036_PLLCON0_POSTDIV1_MASK) >> + RK3036_PLLCON0_POSTDIV1_SHIFT; + fbdiv = (con & RK3036_PLLCON0_FBDIV_MASK) >> + RK3036_PLLCON0_FBDIV_SHIFT; + con = readl(base + pll->con_offset + 0x4); + postdiv2 = (con & RK3036_PLLCON1_POSTDIV2_MASK) >> + RK3036_PLLCON1_POSTDIV2_SHIFT; + refdiv = (con & RK3036_PLLCON1_REFDIV_MASK) >> + RK3036_PLLCON1_REFDIV_SHIFT; + dsmpd = (con & RK3036_PLLCON1_DSMPD_MASK) >> + RK3036_PLLCON1_DSMPD_SHIFT; + con = readl(base + pll->con_offset + 0x8); + frac = (con & RK3036_PLLCON2_FRAC_MASK) >> + RK3036_PLLCON2_FRAC_SHIFT; + rate = (24 * fbdiv / (refdiv * postdiv1 * postdiv2)) * 1000000; + if (dsmpd == 0) { + u64 frac_rate = OSC_HZ * (u64)frac; + + do_div(frac_rate, refdiv); + frac_rate >>= 24; + do_div(frac_rate, postdiv1); + do_div(frac_rate, postdiv1); + rate += frac_rate; + } + return rate; + case RKCLK_PLL_MODE_DEEP: + default: + return 32768; + } +} + +ulong rockchip_pll_get_rate(struct rockchip_pll_clock *pll, + void __iomem *base, + ulong pll_id) +{ + ulong rate = 0; + + switch (pll->type) { + case pll_rk3036: + pll->mode_mask = PLL_MODE_MASK; + rate = rk3036_pll_get_rate(pll, base, pll_id); + break; + case pll_rk3328: + pll->mode_mask = PLL_RK3328_MODE_MASK; + rate = rk3036_pll_get_rate(pll, base, pll_id); + break; + default: + printf("%s: Unknown pll type for pll clk %ld\n", + __func__, pll_id); + } + return rate; +} + +int rockchip_pll_set_rate(struct rockchip_pll_clock *pll, + void __iomem *base, ulong pll_id, + ulong drate) +{ + int ret = 0; + + if (rockchip_pll_get_rate(pll, base, pll_id) == drate) + return 0; + + switch (pll->type) { + case pll_rk3036: + pll->mode_mask = PLL_MODE_MASK; + ret = rk3036_pll_set_rate(pll, base, pll_id, drate); + break; + case pll_rk3328: + pll->mode_mask = PLL_RK3328_MODE_MASK; + ret = rk3036_pll_set_rate(pll, base, pll_id, drate); + break; + default: + printf("%s: Unknown pll type for pll clk %ld\n", + __func__, pll_id); + } + return ret; +} + +const struct rockchip_cpu_rate_table * +rockchip_get_cpu_settings(struct rockchip_cpu_rate_table *cpu_table, + ulong rate) +{ + struct rockchip_cpu_rate_table *ps = cpu_table; + + while (ps->rate) { + if (ps->rate == rate) + break; + ps++; + } + if (ps->rate != rate) + return NULL; + else + return ps; +} + -- 2.25.1