GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# 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
|
||||
obj-$(CONFIG_ROCKCHIP_RK3188) += clk_rk3188.o
|
||||
obj-$(CONFIG_ROCKCHIP_RK322X) += clk_rk322x.o
|
||||
obj-$(CONFIG_ROCKCHIP_RK3288) += clk_rk3288.o
|
||||
obj-$(CONFIG_ROCKCHIP_RK3308) += clk_rk3308.o
|
||||
obj-$(CONFIG_ROCKCHIP_RK3328) += clk_rk3328.o
|
||||
obj-$(CONFIG_ROCKCHIP_RK3368) += clk_rk3368.o
|
||||
obj-$(CONFIG_ROCKCHIP_RK3399) += clk_rk3399.o
|
||||
obj-$(CONFIG_ROCKCHIP_RV1108) += clk_rv1108.o
|
||||
@@ -0,0 +1,360 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2018-2019 Rockchip Electronics Co., Ltd
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <bitfield.h>
|
||||
#include <clk-uclass.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch-rockchip/clock.h>
|
||||
#include <asm/arch-rockchip/hardware.h>
|
||||
#include <div64.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,379 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2015 Google, Inc
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk-uclass.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch-rockchip/clock.h>
|
||||
#include <asm/arch-rockchip/cru_rk3036.h>
|
||||
#include <asm/arch-rockchip/hardware.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dt-bindings/clock/rk3036-cru.h>
|
||||
#include <linux/log2.h>
|
||||
|
||||
enum {
|
||||
VCO_MAX_HZ = 2400U * 1000000,
|
||||
VCO_MIN_HZ = 600 * 1000000,
|
||||
OUTPUT_MAX_HZ = 2400U * 1000000,
|
||||
OUTPUT_MIN_HZ = 24 * 1000000,
|
||||
};
|
||||
|
||||
#define RATE_TO_DIV(input_rate, output_rate) \
|
||||
((input_rate) / (output_rate) - 1);
|
||||
|
||||
#define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1))
|
||||
|
||||
#define PLL_DIVISORS(hz, _refdiv, _postdiv1, _postdiv2) {\
|
||||
.refdiv = _refdiv,\
|
||||
.fbdiv = (u32)((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ),\
|
||||
.postdiv1 = _postdiv1, .postdiv2 = _postdiv2};\
|
||||
_Static_assert(((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ) *\
|
||||
OSC_HZ / (_refdiv * _postdiv1 * _postdiv2) == hz,\
|
||||
#hz "Hz cannot be hit with PLL "\
|
||||
"divisors on line " __stringify(__LINE__));
|
||||
|
||||
/* use integer mode*/
|
||||
static const struct pll_div apll_init_cfg = PLL_DIVISORS(APLL_HZ, 1, 3, 1);
|
||||
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2, 1);
|
||||
|
||||
static int rkclk_set_pll(struct rk3036_cru *cru, enum rk_clk_id clk_id,
|
||||
const struct pll_div *div)
|
||||
{
|
||||
int pll_id = rk_pll_id(clk_id);
|
||||
struct rk3036_pll *pll = &cru->pll[pll_id];
|
||||
|
||||
/* All PLLs have same VCO and output frequency range restrictions. */
|
||||
uint vco_hz = OSC_HZ / 1000 * div->fbdiv / div->refdiv * 1000;
|
||||
uint output_hz = vco_hz / div->postdiv1 / div->postdiv2;
|
||||
|
||||
debug("PLL at %p: fbdiv=%d, refdiv=%d, postdiv1=%d, postdiv2=%d,\
|
||||
vco=%u Hz, output=%u Hz\n",
|
||||
pll, div->fbdiv, div->refdiv, div->postdiv1,
|
||||
div->postdiv2, vco_hz, output_hz);
|
||||
assert(vco_hz >= VCO_MIN_HZ && vco_hz <= VCO_MAX_HZ &&
|
||||
output_hz >= OUTPUT_MIN_HZ && output_hz <= OUTPUT_MAX_HZ);
|
||||
|
||||
/* use integer mode */
|
||||
rk_setreg(&pll->con1, 1 << PLL_DSMPD_SHIFT);
|
||||
|
||||
rk_clrsetreg(&pll->con0,
|
||||
PLL_POSTDIV1_MASK | PLL_FBDIV_MASK,
|
||||
(div->postdiv1 << PLL_POSTDIV1_SHIFT) | div->fbdiv);
|
||||
rk_clrsetreg(&pll->con1, PLL_POSTDIV2_MASK | PLL_REFDIV_MASK,
|
||||
(div->postdiv2 << PLL_POSTDIV2_SHIFT |
|
||||
div->refdiv << PLL_REFDIV_SHIFT));
|
||||
|
||||
/* waiting for pll lock */
|
||||
while (readl(&pll->con1) & (1 << PLL_LOCK_STATUS_SHIFT))
|
||||
udelay(1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rkclk_init(struct rk3036_cru *cru)
|
||||
{
|
||||
u32 aclk_div;
|
||||
u32 hclk_div;
|
||||
u32 pclk_div;
|
||||
|
||||
/* pll enter slow-mode */
|
||||
rk_clrsetreg(&cru->cru_mode_con,
|
||||
GPLL_MODE_MASK | APLL_MODE_MASK,
|
||||
GPLL_MODE_SLOW << GPLL_MODE_SHIFT |
|
||||
APLL_MODE_SLOW << APLL_MODE_SHIFT);
|
||||
|
||||
/* init pll */
|
||||
rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg);
|
||||
rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);
|
||||
|
||||
/*
|
||||
* select apll as cpu/core clock pll source and
|
||||
* set up dependent divisors for PERI and ACLK clocks.
|
||||
* core hz : apll = 1:1
|
||||
*/
|
||||
aclk_div = APLL_HZ / CORE_ACLK_HZ - 1;
|
||||
assert((aclk_div + 1) * CORE_ACLK_HZ == APLL_HZ && aclk_div < 0x7);
|
||||
|
||||
pclk_div = APLL_HZ / CORE_PERI_HZ - 1;
|
||||
assert((pclk_div + 1) * CORE_PERI_HZ == APLL_HZ && pclk_div < 0xf);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[0],
|
||||
CORE_CLK_PLL_SEL_MASK | CORE_DIV_CON_MASK,
|
||||
CORE_CLK_PLL_SEL_APLL << CORE_CLK_PLL_SEL_SHIFT |
|
||||
0 << CORE_DIV_CON_SHIFT);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[1],
|
||||
CORE_ACLK_DIV_MASK | CORE_PERI_DIV_MASK,
|
||||
aclk_div << CORE_ACLK_DIV_SHIFT |
|
||||
pclk_div << CORE_PERI_DIV_SHIFT);
|
||||
|
||||
/*
|
||||
* select apll as pd_bus bus clock source and
|
||||
* set up dependent divisors for PCLK/HCLK and ACLK clocks.
|
||||
*/
|
||||
aclk_div = GPLL_HZ / BUS_ACLK_HZ - 1;
|
||||
assert((aclk_div + 1) * BUS_ACLK_HZ == GPLL_HZ && aclk_div <= 0x1f);
|
||||
|
||||
pclk_div = GPLL_HZ / BUS_PCLK_HZ - 1;
|
||||
assert((pclk_div + 1) * BUS_PCLK_HZ == GPLL_HZ && pclk_div <= 0x7);
|
||||
|
||||
hclk_div = GPLL_HZ / BUS_HCLK_HZ - 1;
|
||||
assert((hclk_div + 1) * BUS_HCLK_HZ == GPLL_HZ && hclk_div <= 0x3);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[0],
|
||||
BUS_ACLK_PLL_SEL_MASK | BUS_ACLK_DIV_MASK,
|
||||
BUS_ACLK_PLL_SEL_GPLL << BUS_ACLK_PLL_SEL_SHIFT |
|
||||
aclk_div << BUS_ACLK_DIV_SHIFT);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[1],
|
||||
BUS_PCLK_DIV_MASK | BUS_HCLK_DIV_MASK,
|
||||
pclk_div << BUS_PCLK_DIV_SHIFT |
|
||||
hclk_div << BUS_HCLK_DIV_SHIFT);
|
||||
|
||||
/*
|
||||
* select gpll as pd_peri bus clock source 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 < 0x8);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[10],
|
||||
PERI_PLL_SEL_MASK | PERI_PCLK_DIV_MASK |
|
||||
PERI_HCLK_DIV_MASK | PERI_ACLK_DIV_MASK,
|
||||
PERI_PLL_GPLL << PERI_PLL_SEL_SHIFT |
|
||||
pclk_div << PERI_PCLK_DIV_SHIFT |
|
||||
hclk_div << PERI_HCLK_DIV_SHIFT |
|
||||
aclk_div << PERI_ACLK_DIV_SHIFT);
|
||||
|
||||
/* PLL enter normal-mode */
|
||||
rk_clrsetreg(&cru->cru_mode_con,
|
||||
GPLL_MODE_MASK | APLL_MODE_MASK,
|
||||
GPLL_MODE_NORM << GPLL_MODE_SHIFT |
|
||||
APLL_MODE_NORM << APLL_MODE_SHIFT);
|
||||
}
|
||||
|
||||
/* Get pll rate by id */
|
||||
static uint32_t rkclk_pll_get_rate(struct rk3036_cru *cru,
|
||||
enum rk_clk_id clk_id)
|
||||
{
|
||||
uint32_t refdiv, fbdiv, postdiv1, postdiv2;
|
||||
uint32_t con;
|
||||
int pll_id = rk_pll_id(clk_id);
|
||||
struct rk3036_pll *pll = &cru->pll[pll_id];
|
||||
static u8 clk_shift[CLK_COUNT] = {
|
||||
0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, 0xff,
|
||||
GPLL_MODE_SHIFT, 0xff
|
||||
};
|
||||
static u32 clk_mask[CLK_COUNT] = {
|
||||
0xffffffff, APLL_MODE_MASK, DPLL_MODE_MASK, 0xffffffff,
|
||||
GPLL_MODE_MASK, 0xffffffff
|
||||
};
|
||||
uint shift;
|
||||
uint mask;
|
||||
|
||||
con = readl(&cru->cru_mode_con);
|
||||
shift = clk_shift[clk_id];
|
||||
mask = clk_mask[clk_id];
|
||||
|
||||
switch ((con & mask) >> shift) {
|
||||
case GPLL_MODE_SLOW:
|
||||
return OSC_HZ;
|
||||
case GPLL_MODE_NORM:
|
||||
|
||||
/* normal mode */
|
||||
con = readl(&pll->con0);
|
||||
postdiv1 = (con & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT;
|
||||
fbdiv = (con & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT;
|
||||
con = readl(&pll->con1);
|
||||
postdiv2 = (con & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT;
|
||||
refdiv = (con & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT;
|
||||
return (24 * fbdiv / (refdiv * postdiv1 * postdiv2)) * 1000000;
|
||||
case GPLL_MODE_DEEP:
|
||||
default:
|
||||
return 32768;
|
||||
}
|
||||
}
|
||||
|
||||
static ulong rockchip_mmc_get_clk(struct rk3036_cru *cru, uint clk_general_rate,
|
||||
int periph)
|
||||
{
|
||||
uint src_rate;
|
||||
uint div, mux;
|
||||
u32 con;
|
||||
|
||||
switch (periph) {
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
con = readl(&cru->cru_clksel_con[12]);
|
||||
mux = (con & EMMC_PLL_MASK) >> EMMC_PLL_SHIFT;
|
||||
div = (con & EMMC_DIV_MASK) >> EMMC_DIV_SHIFT;
|
||||
break;
|
||||
case HCLK_SDIO:
|
||||
case SCLK_SDIO:
|
||||
con = readl(&cru->cru_clksel_con[12]);
|
||||
mux = (con & MMC0_PLL_MASK) >> MMC0_PLL_SHIFT;
|
||||
div = (con & MMC0_DIV_MASK) >> MMC0_DIV_SHIFT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
src_rate = mux == EMMC_SEL_24M ? OSC_HZ : clk_general_rate;
|
||||
return DIV_TO_RATE(src_rate, div) / 2;
|
||||
}
|
||||
|
||||
static ulong rockchip_mmc_set_clk(struct rk3036_cru *cru, uint clk_general_rate,
|
||||
int periph, uint freq)
|
||||
{
|
||||
int src_clk_div;
|
||||
int mux;
|
||||
|
||||
debug("%s: clk_general_rate=%u\n", __func__, clk_general_rate);
|
||||
|
||||
/* mmc clock auto divide 2 in internal */
|
||||
src_clk_div = DIV_ROUND_UP(clk_general_rate / 2, freq);
|
||||
|
||||
if (src_clk_div > 128) {
|
||||
src_clk_div = DIV_ROUND_UP(OSC_HZ / 2, freq);
|
||||
assert(src_clk_div - 1 < 128);
|
||||
mux = EMMC_SEL_24M;
|
||||
} else {
|
||||
mux = EMMC_SEL_GPLL;
|
||||
}
|
||||
|
||||
switch (periph) {
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[12],
|
||||
EMMC_PLL_MASK | EMMC_DIV_MASK,
|
||||
mux << EMMC_PLL_SHIFT |
|
||||
(src_clk_div - 1) << EMMC_DIV_SHIFT);
|
||||
break;
|
||||
case HCLK_SDIO:
|
||||
case SCLK_SDIO:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[11],
|
||||
MMC0_PLL_MASK | MMC0_DIV_MASK,
|
||||
mux << MMC0_PLL_SHIFT |
|
||||
(src_clk_div - 1) << MMC0_DIV_SHIFT);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return rockchip_mmc_get_clk(cru, clk_general_rate, periph);
|
||||
}
|
||||
|
||||
static ulong rk3036_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
struct rk3036_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
|
||||
switch (clk->id) {
|
||||
case 0 ... 63:
|
||||
return rkclk_pll_get_rate(priv->cru, clk->id);
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
static ulong rk3036_clk_set_rate(struct clk *clk, ulong rate)
|
||||
{
|
||||
struct rk3036_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 0 ... 63:
|
||||
return 0;
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
new_rate = rockchip_mmc_set_clk(priv->cru, gclk_rate,
|
||||
clk->id, rate);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return new_rate;
|
||||
}
|
||||
|
||||
static struct clk_ops rk3036_clk_ops = {
|
||||
.get_rate = rk3036_clk_get_rate,
|
||||
.set_rate = rk3036_clk_set_rate,
|
||||
};
|
||||
|
||||
static int rk3036_clk_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct rk3036_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->cru = dev_read_addr_ptr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3036_clk_probe(struct udevice *dev)
|
||||
{
|
||||
struct rk3036_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
rkclk_init(priv->cru);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3036_clk_bind(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct udevice *sys_child;
|
||||
struct sysreset_reg *priv;
|
||||
|
||||
/* The reset driver does not have a device node, so bind it here */
|
||||
ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
|
||||
&sys_child);
|
||||
if (ret) {
|
||||
debug("Warning: No sysreset driver: ret=%d\n", ret);
|
||||
} else {
|
||||
priv = malloc(sizeof(struct sysreset_reg));
|
||||
priv->glb_srst_fst_value = offsetof(struct rk3036_cru,
|
||||
cru_glb_srst_fst_value);
|
||||
priv->glb_srst_snd_value = offsetof(struct rk3036_cru,
|
||||
cru_glb_srst_snd_value);
|
||||
sys_child->priv = priv;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(RESET_ROCKCHIP)
|
||||
ret = offsetof(struct rk3036_cru, cru_softrst_con[0]);
|
||||
ret = rockchip_reset_bind(dev, ret, 9);
|
||||
if (ret)
|
||||
debug("Warning: software reset driver bind faile\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id rk3036_clk_ids[] = {
|
||||
{ .compatible = "rockchip,rk3036-cru" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_rk3036_cru) = {
|
||||
.name = "clk_rk3036",
|
||||
.id = UCLASS_CLK,
|
||||
.of_match = rk3036_clk_ids,
|
||||
.priv_auto_alloc_size = sizeof(struct rk3036_clk_priv),
|
||||
.ofdata_to_platdata = rk3036_clk_ofdata_to_platdata,
|
||||
.ops = &rk3036_clk_ops,
|
||||
.bind = rk3036_clk_bind,
|
||||
.probe = rk3036_clk_probe,
|
||||
};
|
||||
@@ -0,0 +1,602 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk-uclass.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch-rockchip/clock.h>
|
||||
#include <asm/arch-rockchip/cru_rk3128.h>
|
||||
#include <asm/arch-rockchip/hardware.h>
|
||||
#include <bitfield.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dt-bindings/clock/rk3128-cru.h>
|
||||
#include <linux/log2.h>
|
||||
|
||||
enum {
|
||||
VCO_MAX_HZ = 2400U * 1000000,
|
||||
VCO_MIN_HZ = 600 * 1000000,
|
||||
OUTPUT_MAX_HZ = 2400U * 1000000,
|
||||
OUTPUT_MIN_HZ = 24 * 1000000,
|
||||
};
|
||||
|
||||
#define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1))
|
||||
|
||||
#define PLL_DIVISORS(hz, _refdiv, _postdiv1, _postdiv2) {\
|
||||
.refdiv = _refdiv,\
|
||||
.fbdiv = (u32)((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ),\
|
||||
.postdiv1 = _postdiv1, .postdiv2 = _postdiv2};
|
||||
|
||||
/* use integer mode*/
|
||||
static const struct pll_div apll_init_cfg = PLL_DIVISORS(APLL_HZ, 1, 3, 1);
|
||||
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2, 1);
|
||||
|
||||
static int rkclk_set_pll(struct rk3128_cru *cru, enum rk_clk_id clk_id,
|
||||
const struct pll_div *div)
|
||||
{
|
||||
int pll_id = rk_pll_id(clk_id);
|
||||
struct rk3128_pll *pll = &cru->pll[pll_id];
|
||||
|
||||
/* All PLLs have same VCO and output frequency range restrictions. */
|
||||
uint vco_hz = OSC_HZ / 1000 * div->fbdiv / div->refdiv * 1000;
|
||||
uint output_hz = vco_hz / div->postdiv1 / div->postdiv2;
|
||||
|
||||
debug("PLL at %p:fd=%d,rd=%d,pd1=%d,pd2=%d,vco=%uHz,output=%uHz\n",
|
||||
pll, div->fbdiv, div->refdiv, div->postdiv1,
|
||||
div->postdiv2, vco_hz, output_hz);
|
||||
assert(vco_hz >= VCO_MIN_HZ && vco_hz <= VCO_MAX_HZ &&
|
||||
output_hz >= OUTPUT_MIN_HZ && output_hz <= OUTPUT_MAX_HZ);
|
||||
|
||||
/* use integer mode */
|
||||
rk_setreg(&pll->con1, 1 << PLL_DSMPD_SHIFT);
|
||||
/* Power down */
|
||||
rk_setreg(&pll->con1, 1 << PLL_PD_SHIFT);
|
||||
|
||||
rk_clrsetreg(&pll->con0,
|
||||
PLL_POSTDIV1_MASK | PLL_FBDIV_MASK,
|
||||
(div->postdiv1 << PLL_POSTDIV1_SHIFT) | div->fbdiv);
|
||||
rk_clrsetreg(&pll->con1, PLL_POSTDIV2_MASK | PLL_REFDIV_MASK,
|
||||
(div->postdiv2 << PLL_POSTDIV2_SHIFT |
|
||||
div->refdiv << PLL_REFDIV_SHIFT));
|
||||
|
||||
/* Power Up */
|
||||
rk_clrreg(&pll->con1, 1 << PLL_PD_SHIFT);
|
||||
|
||||
/* waiting for pll lock */
|
||||
while (readl(&pll->con1) & (1 << PLL_LOCK_STATUS_SHIFT))
|
||||
udelay(1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pll_para_config(u32 freq_hz, struct pll_div *div)
|
||||
{
|
||||
u32 ref_khz = OSC_HZ / 1000, refdiv, fbdiv = 0;
|
||||
u32 postdiv1, postdiv2 = 1;
|
||||
u32 fref_khz;
|
||||
u32 diff_khz, best_diff_khz;
|
||||
const u32 max_refdiv = 63, max_fbdiv = 3200, min_fbdiv = 16;
|
||||
const u32 max_postdiv1 = 7, max_postdiv2 = 7;
|
||||
u32 vco_khz;
|
||||
u32 freq_khz = freq_hz / 1000;
|
||||
|
||||
if (!freq_hz) {
|
||||
printf("%s: the frequency can't be 0 Hz\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
postdiv1 = DIV_ROUND_UP(VCO_MIN_HZ / 1000, freq_khz);
|
||||
if (postdiv1 > max_postdiv1) {
|
||||
postdiv2 = DIV_ROUND_UP(postdiv1, max_postdiv1);
|
||||
postdiv1 = DIV_ROUND_UP(postdiv1, postdiv2);
|
||||
}
|
||||
|
||||
vco_khz = freq_khz * postdiv1 * postdiv2;
|
||||
|
||||
if (vco_khz < (VCO_MIN_HZ / 1000) || vco_khz > (VCO_MAX_HZ / 1000) ||
|
||||
postdiv2 > max_postdiv2) {
|
||||
printf("%s: Cannot find out a supported VCO for Freq (%uHz)\n",
|
||||
__func__, freq_hz);
|
||||
return -1;
|
||||
}
|
||||
|
||||
div->postdiv1 = postdiv1;
|
||||
div->postdiv2 = postdiv2;
|
||||
|
||||
best_diff_khz = vco_khz;
|
||||
for (refdiv = 1; refdiv < max_refdiv && best_diff_khz; refdiv++) {
|
||||
fref_khz = ref_khz / refdiv;
|
||||
|
||||
fbdiv = vco_khz / fref_khz;
|
||||
if ((fbdiv >= max_fbdiv) || (fbdiv <= min_fbdiv))
|
||||
continue;
|
||||
diff_khz = vco_khz - fbdiv * fref_khz;
|
||||
if (fbdiv + 1 < max_fbdiv && diff_khz > fref_khz / 2) {
|
||||
fbdiv++;
|
||||
diff_khz = fref_khz - diff_khz;
|
||||
}
|
||||
|
||||
if (diff_khz >= best_diff_khz)
|
||||
continue;
|
||||
|
||||
best_diff_khz = diff_khz;
|
||||
div->refdiv = refdiv;
|
||||
div->fbdiv = fbdiv;
|
||||
}
|
||||
|
||||
if (best_diff_khz > 4 * (1000)) {
|
||||
printf("%s: Failed to match output frequency %u bestis %u Hz\n",
|
||||
__func__, freq_hz,
|
||||
best_diff_khz * 1000);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rkclk_init(struct rk3128_cru *cru)
|
||||
{
|
||||
u32 aclk_div;
|
||||
u32 hclk_div;
|
||||
u32 pclk_div;
|
||||
|
||||
/* pll enter slow-mode */
|
||||
rk_clrsetreg(&cru->cru_mode_con,
|
||||
GPLL_MODE_MASK | APLL_MODE_MASK,
|
||||
GPLL_MODE_SLOW << GPLL_MODE_SHIFT |
|
||||
APLL_MODE_SLOW << APLL_MODE_SHIFT);
|
||||
|
||||
/* init pll */
|
||||
rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg);
|
||||
rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);
|
||||
|
||||
/*
|
||||
* select apll as cpu/core clock pll source and
|
||||
* set up dependent divisors for PERI and ACLK clocks.
|
||||
* core hz : apll = 1:1
|
||||
*/
|
||||
aclk_div = APLL_HZ / CORE_ACLK_HZ - 1;
|
||||
assert((aclk_div + 1) * CORE_ACLK_HZ == APLL_HZ && aclk_div < 0x7);
|
||||
|
||||
pclk_div = APLL_HZ / CORE_PERI_HZ - 1;
|
||||
assert((pclk_div + 1) * CORE_PERI_HZ == APLL_HZ && pclk_div < 0xf);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[0],
|
||||
CORE_CLK_PLL_SEL_MASK | CORE_DIV_CON_MASK,
|
||||
CORE_CLK_PLL_SEL_APLL << CORE_CLK_PLL_SEL_SHIFT |
|
||||
0 << CORE_DIV_CON_SHIFT);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[1],
|
||||
CORE_ACLK_DIV_MASK | CORE_PERI_DIV_MASK,
|
||||
aclk_div << CORE_ACLK_DIV_SHIFT |
|
||||
pclk_div << CORE_PERI_DIV_SHIFT);
|
||||
|
||||
/*
|
||||
* select gpll as pd_bus bus clock source and
|
||||
* set up dependent divisors for PCLK/HCLK and ACLK clocks.
|
||||
*/
|
||||
aclk_div = GPLL_HZ / BUS_ACLK_HZ - 1;
|
||||
assert((aclk_div + 1) * BUS_ACLK_HZ == GPLL_HZ && aclk_div <= 0x1f);
|
||||
|
||||
pclk_div = BUS_ACLK_HZ / BUS_PCLK_HZ - 1;
|
||||
assert((pclk_div + 1) * BUS_PCLK_HZ == BUS_ACLK_HZ && pclk_div <= 0x7);
|
||||
|
||||
hclk_div = BUS_ACLK_HZ / BUS_HCLK_HZ - 1;
|
||||
assert((hclk_div + 1) * BUS_HCLK_HZ == BUS_ACLK_HZ && hclk_div <= 0x3);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[0],
|
||||
BUS_ACLK_PLL_SEL_MASK | BUS_ACLK_DIV_MASK,
|
||||
BUS_ACLK_PLL_SEL_GPLL << BUS_ACLK_PLL_SEL_SHIFT |
|
||||
aclk_div << BUS_ACLK_DIV_SHIFT);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[1],
|
||||
BUS_PCLK_DIV_MASK | BUS_HCLK_DIV_MASK,
|
||||
pclk_div << BUS_PCLK_DIV_SHIFT |
|
||||
hclk_div << BUS_HCLK_DIV_SHIFT);
|
||||
|
||||
/*
|
||||
* select gpll as pd_peri bus clock source 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 < 0x8);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[10],
|
||||
PERI_PLL_SEL_MASK | PERI_PCLK_DIV_MASK |
|
||||
PERI_HCLK_DIV_MASK | PERI_ACLK_DIV_MASK,
|
||||
PERI_PLL_GPLL << PERI_PLL_SEL_SHIFT |
|
||||
pclk_div << PERI_PCLK_DIV_SHIFT |
|
||||
hclk_div << PERI_HCLK_DIV_SHIFT |
|
||||
aclk_div << PERI_ACLK_DIV_SHIFT);
|
||||
|
||||
/* PLL enter normal-mode */
|
||||
rk_clrsetreg(&cru->cru_mode_con,
|
||||
GPLL_MODE_MASK | APLL_MODE_MASK | CPLL_MODE_MASK,
|
||||
GPLL_MODE_NORM << GPLL_MODE_SHIFT |
|
||||
APLL_MODE_NORM << APLL_MODE_SHIFT |
|
||||
CPLL_MODE_NORM << CPLL_MODE_SHIFT);
|
||||
|
||||
/*fix NAND controller working clock max to 150Mhz */
|
||||
rk_clrsetreg(&cru->cru_clksel_con[2],
|
||||
NANDC_PLL_SEL_MASK | NANDC_CLK_DIV_MASK,
|
||||
NANDC_PLL_SEL_GPLL << NANDC_PLL_SEL_SHIFT |
|
||||
3 << NANDC_CLK_DIV_SHIFT);
|
||||
}
|
||||
|
||||
/* Get pll rate by id */
|
||||
static u32 rkclk_pll_get_rate(struct rk3128_cru *cru,
|
||||
enum rk_clk_id clk_id)
|
||||
{
|
||||
u32 refdiv, fbdiv, postdiv1, postdiv2;
|
||||
u32 con;
|
||||
int pll_id = rk_pll_id(clk_id);
|
||||
struct rk3128_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, 0xff
|
||||
};
|
||||
static u32 clk_mask[CLK_COUNT] = {
|
||||
0xff, APLL_MODE_MASK, DPLL_MODE_MASK, CPLL_MODE_MASK,
|
||||
GPLL_MODE_MASK, 0xff
|
||||
};
|
||||
uint shift;
|
||||
uint mask;
|
||||
|
||||
con = readl(&cru->cru_mode_con);
|
||||
shift = clk_shift[clk_id];
|
||||
mask = clk_mask[clk_id];
|
||||
|
||||
switch ((con & mask) >> shift) {
|
||||
case GPLL_MODE_SLOW:
|
||||
return OSC_HZ;
|
||||
case GPLL_MODE_NORM:
|
||||
/* normal mode */
|
||||
con = readl(&pll->con0);
|
||||
postdiv1 = (con & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT;
|
||||
fbdiv = (con & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT;
|
||||
con = readl(&pll->con1);
|
||||
postdiv2 = (con & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT;
|
||||
refdiv = (con & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT;
|
||||
return (24 * fbdiv / (refdiv * postdiv1 * postdiv2)) * 1000000;
|
||||
case GPLL_MODE_DEEP:
|
||||
default:
|
||||
return 32768;
|
||||
}
|
||||
}
|
||||
|
||||
static ulong rockchip_mmc_get_clk(struct rk3128_cru *cru, uint clk_general_rate,
|
||||
int periph)
|
||||
{
|
||||
uint src_rate;
|
||||
uint div, mux;
|
||||
u32 con;
|
||||
|
||||
switch (periph) {
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
case SCLK_EMMC_SAMPLE:
|
||||
con = readl(&cru->cru_clksel_con[12]);
|
||||
mux = (con & EMMC_PLL_MASK) >> EMMC_PLL_SHIFT;
|
||||
div = (con & EMMC_DIV_MASK) >> EMMC_DIV_SHIFT;
|
||||
break;
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
con = readl(&cru->cru_clksel_con[11]);
|
||||
mux = (con & MMC0_PLL_MASK) >> MMC0_PLL_SHIFT;
|
||||
div = (con & MMC0_DIV_MASK) >> MMC0_DIV_SHIFT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
src_rate = mux == EMMC_SEL_24M ? OSC_HZ : clk_general_rate;
|
||||
return DIV_TO_RATE(src_rate, div);
|
||||
}
|
||||
|
||||
static ulong rockchip_mmc_set_clk(struct rk3128_cru *cru, uint clk_general_rate,
|
||||
int periph, uint freq)
|
||||
{
|
||||
int src_clk_div;
|
||||
int mux;
|
||||
|
||||
debug("%s: clk_general_rate=%u\n", __func__, clk_general_rate);
|
||||
|
||||
/* mmc clock defaulg div 2 internal, need provide double in cru */
|
||||
src_clk_div = DIV_ROUND_UP(clk_general_rate / 2, freq);
|
||||
|
||||
if (src_clk_div > 128) {
|
||||
src_clk_div = DIV_ROUND_UP(OSC_HZ / 2, freq);
|
||||
mux = EMMC_SEL_24M;
|
||||
} else {
|
||||
mux = EMMC_SEL_GPLL;
|
||||
}
|
||||
|
||||
switch (periph) {
|
||||
case HCLK_EMMC:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[12],
|
||||
EMMC_PLL_MASK | EMMC_DIV_MASK,
|
||||
mux << EMMC_PLL_SHIFT |
|
||||
(src_clk_div - 1) << EMMC_DIV_SHIFT);
|
||||
break;
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[11],
|
||||
MMC0_PLL_MASK | MMC0_DIV_MASK,
|
||||
mux << MMC0_PLL_SHIFT |
|
||||
(src_clk_div - 1) << MMC0_DIV_SHIFT);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return rockchip_mmc_get_clk(cru, clk_general_rate, periph);
|
||||
}
|
||||
|
||||
static ulong rk3128_peri_get_pclk(struct rk3128_cru *cru, ulong clk_id)
|
||||
{
|
||||
u32 div, con;
|
||||
|
||||
switch (clk_id) {
|
||||
case PCLK_I2C0:
|
||||
case PCLK_I2C1:
|
||||
case PCLK_I2C2:
|
||||
case PCLK_I2C3:
|
||||
case PCLK_PWM:
|
||||
con = readl(&cru->cru_clksel_con[10]);
|
||||
div = con >> 12 & 0x3;
|
||||
break;
|
||||
default:
|
||||
printf("do not support this peripheral bus\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return DIV_TO_RATE(PERI_ACLK_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rk3128_peri_set_pclk(struct rk3128_cru *cru, ulong clk_id, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = PERI_ACLK_HZ / hz;
|
||||
assert(src_clk_div - 1 < 4);
|
||||
|
||||
switch (clk_id) {
|
||||
case PCLK_I2C0:
|
||||
case PCLK_I2C1:
|
||||
case PCLK_I2C2:
|
||||
case PCLK_I2C3:
|
||||
case PCLK_PWM:
|
||||
rk_setreg(&cru->cru_clksel_con[10],
|
||||
((src_clk_div - 1) << 12));
|
||||
break;
|
||||
default:
|
||||
printf("do not support this peripheral bus\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return DIV_TO_RATE(PERI_ACLK_HZ, src_clk_div);
|
||||
}
|
||||
|
||||
static ulong rk3128_saradc_get_clk(struct rk3128_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
|
||||
val = readl(&cru->cru_clksel_con[24]);
|
||||
div = bitfield_extract(val, SARADC_DIV_CON_SHIFT,
|
||||
SARADC_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(OSC_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rk3128_saradc_set_clk(struct rk3128_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(OSC_HZ, hz) - 1;
|
||||
assert(src_clk_div < 128);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[24],
|
||||
SARADC_DIV_CON_MASK,
|
||||
src_clk_div << SARADC_DIV_CON_SHIFT);
|
||||
|
||||
return rk3128_saradc_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rk3128_vop_set_clk(struct rk3128_cru *cru, ulong clk_id, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
struct pll_div cpll_config = {0};
|
||||
|
||||
src_clk_div = GPLL_HZ / hz;
|
||||
assert(src_clk_div - 1 < 31);
|
||||
|
||||
switch (clk_id) {
|
||||
case ACLK_VIO0:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[31],
|
||||
VIO0_PLL_MASK | VIO0_DIV_MASK,
|
||||
VIO0_SEL_GPLL << VIO0_PLL_SHIFT |
|
||||
(src_clk_div - 1) << VIO0_DIV_SHIFT);
|
||||
break;
|
||||
case ACLK_VIO1:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[31],
|
||||
VIO1_PLL_MASK | VIO1_DIV_MASK,
|
||||
VIO1_SEL_GPLL << VIO1_PLL_SHIFT |
|
||||
(src_clk_div - 1) << VIO1_DIV_SHIFT);
|
||||
break;
|
||||
case DCLK_LCDC:
|
||||
if (pll_para_config(hz, &cpll_config))
|
||||
return -1;
|
||||
rkclk_set_pll(cru, CLK_CODEC, &cpll_config);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[27],
|
||||
DCLK_VOP_SEL_MASK | DCLK_VOP_DIV_CON_MASK,
|
||||
DCLK_VOP_PLL_SEL_CPLL << DCLK_VOP_SEL_SHIFT |
|
||||
(1 - 1) << DCLK_VOP_DIV_CON_SHIFT);
|
||||
break;
|
||||
default:
|
||||
printf("do not support this vop freq\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return hz;
|
||||
}
|
||||
|
||||
static ulong rk3128_vop_get_rate(struct rk3128_cru *cru, ulong clk_id)
|
||||
{
|
||||
u32 div, con, parent;
|
||||
|
||||
switch (clk_id) {
|
||||
case ACLK_VIO0:
|
||||
con = readl(&cru->cru_clksel_con[31]);
|
||||
div = con & 0x1f;
|
||||
parent = GPLL_HZ;
|
||||
break;
|
||||
case ACLK_VIO1:
|
||||
con = readl(&cru->cru_clksel_con[31]);
|
||||
div = (con >> 8) & 0x1f;
|
||||
parent = GPLL_HZ;
|
||||
break;
|
||||
case DCLK_LCDC:
|
||||
con = readl(&cru->cru_clksel_con[27]);
|
||||
div = (con >> 8) & 0xfff;
|
||||
parent = rkclk_pll_get_rate(cru, CLK_CODEC);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
return DIV_TO_RATE(parent, div);
|
||||
}
|
||||
|
||||
static ulong rk3128_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
struct rk3128_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
|
||||
switch (clk->id) {
|
||||
case 0 ... 63:
|
||||
return rkclk_pll_get_rate(priv->cru, clk->id);
|
||||
case PCLK_I2C0:
|
||||
case PCLK_I2C1:
|
||||
case PCLK_I2C2:
|
||||
case PCLK_I2C3:
|
||||
case PCLK_PWM:
|
||||
return rk3128_peri_get_pclk(priv->cru, clk->id);
|
||||
case SCLK_SARADC:
|
||||
return rk3128_saradc_get_clk(priv->cru);
|
||||
case DCLK_LCDC:
|
||||
case ACLK_VIO0:
|
||||
case ACLK_VIO1:
|
||||
return rk3128_vop_get_rate(priv->cru, clk->id);
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
static ulong rk3128_clk_set_rate(struct clk *clk, ulong rate)
|
||||
{
|
||||
struct rk3128_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 0 ... 63:
|
||||
return 0;
|
||||
case DCLK_LCDC:
|
||||
case ACLK_VIO0:
|
||||
case ACLK_VIO1:
|
||||
new_rate = rk3128_vop_set_clk(priv->cru,
|
||||
clk->id, rate);
|
||||
break;
|
||||
case HCLK_EMMC:
|
||||
new_rate = rockchip_mmc_set_clk(priv->cru, gclk_rate,
|
||||
clk->id, rate);
|
||||
break;
|
||||
case PCLK_I2C0:
|
||||
case PCLK_I2C1:
|
||||
case PCLK_I2C2:
|
||||
case PCLK_I2C3:
|
||||
case PCLK_PWM:
|
||||
new_rate = rk3128_peri_set_pclk(priv->cru, clk->id, rate);
|
||||
break;
|
||||
case SCLK_SARADC:
|
||||
new_rate = rk3128_saradc_set_clk(priv->cru, rate);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return new_rate;
|
||||
}
|
||||
|
||||
static struct clk_ops rk3128_clk_ops = {
|
||||
.get_rate = rk3128_clk_get_rate,
|
||||
.set_rate = rk3128_clk_set_rate,
|
||||
};
|
||||
|
||||
static int rk3128_clk_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct rk3128_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->cru = dev_read_addr_ptr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3128_clk_probe(struct udevice *dev)
|
||||
{
|
||||
struct rk3128_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
rkclk_init(priv->cru);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3128_clk_bind(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct udevice *sys_child;
|
||||
struct sysreset_reg *priv;
|
||||
|
||||
/* The reset driver does not have a device node, so bind it here */
|
||||
ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
|
||||
&sys_child);
|
||||
if (ret) {
|
||||
debug("Warning: No sysreset driver: ret=%d\n", ret);
|
||||
} else {
|
||||
priv = malloc(sizeof(struct sysreset_reg));
|
||||
priv->glb_srst_fst_value = offsetof(struct rk3128_cru,
|
||||
cru_glb_srst_fst_value);
|
||||
priv->glb_srst_snd_value = offsetof(struct rk3128_cru,
|
||||
cru_glb_srst_snd_value);
|
||||
sys_child->priv = priv;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id rk3128_clk_ids[] = {
|
||||
{ .compatible = "rockchip,rk3128-cru" },
|
||||
{ .compatible = "rockchip,rk3126-cru" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_rk3128_cru) = {
|
||||
.name = "clk_rk3128",
|
||||
.id = UCLASS_CLK,
|
||||
.of_match = rk3128_clk_ids,
|
||||
.priv_auto_alloc_size = sizeof(struct rk3128_clk_priv),
|
||||
.ofdata_to_platdata = rk3128_clk_ofdata_to_platdata,
|
||||
.ops = &rk3128_clk_ops,
|
||||
.bind = rk3128_clk_bind,
|
||||
.probe = rk3128_clk_probe,
|
||||
};
|
||||
@@ -0,0 +1,619 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2015 Google, Inc
|
||||
* (C) Copyright 2016 Heiko Stuebner <heiko@sntech.de>
|
||||
*/
|
||||
|
||||
#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-rockchip/clock.h>
|
||||
#include <asm/arch-rockchip/cru_rk3188.h>
|
||||
#include <asm/arch-rockchip/grf_rk3188.h>
|
||||
#include <asm/arch-rockchip/hardware.h>
|
||||
#include <dt-bindings/clock/rk3188-cru.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
#include <linux/log2.h>
|
||||
|
||||
enum rk3188_clk_type {
|
||||
RK3188_CRU,
|
||||
RK3188A_CRU,
|
||||
};
|
||||
|
||||
struct rk3188_clk_plat {
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
struct dtd_rockchip_rk3188_cru dtd;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct pll_div {
|
||||
u32 nr;
|
||||
u32 nf;
|
||||
u32 no;
|
||||
};
|
||||
|
||||
enum {
|
||||
VCO_MAX_HZ = 2200U * 1000000,
|
||||
VCO_MIN_HZ = 440 * 1000000,
|
||||
OUTPUT_MAX_HZ = 2200U * 1000000,
|
||||
OUTPUT_MIN_HZ = 30 * 1000000,
|
||||
FREF_MAX_HZ = 2200U * 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 << 5,
|
||||
SOCSTS_APLL_LOCK = 1 << 6,
|
||||
SOCSTS_CPLL_LOCK = 1 << 7,
|
||||
SOCSTS_GPLL_LOCK = 1 << 8,
|
||||
};
|
||||
|
||||
#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_SPL_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 rk3188_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 rk3188_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 << CLKR_SHIFT | 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 rk3188_cru *cru, struct rk3188_grf *grf,
|
||||
unsigned int hz, bool has_bwadj)
|
||||
{
|
||||
static const struct pll_div dpll_cfg[] = {
|
||||
{.nf = 75, .nr = 1, .no = 6},
|
||||
{.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_SHIFT,
|
||||
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_SHIFT,
|
||||
DPLL_MODE_NORMAL << DPLL_MODE_SHIFT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rkclk_configure_cpu(struct rk3188_cru *cru, struct rk3188_grf *grf,
|
||||
unsigned int hz, bool has_bwadj)
|
||||
{
|
||||
static const struct pll_div apll_cfg[] = {
|
||||
{.nf = 50, .nr = 1, .no = 2},
|
||||
{.nf = 67, .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 in SPL to get away from the 24MHz default and
|
||||
* the maximum of 1.6Ghz, 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_SHIFT,
|
||||
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 << CORE_PERI_DIV_SHIFT,
|
||||
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 << CORE_ACLK_DIV_SHIFT,
|
||||
div_aclk_core << CORE_ACLK_DIV_SHIFT);
|
||||
|
||||
/* PLL enter normal-mode */
|
||||
rk_clrsetreg(&cru->cru_mode_con, APLL_MODE_MASK << APLL_MODE_SHIFT,
|
||||
APLL_MODE_NORMAL << APLL_MODE_SHIFT);
|
||||
|
||||
return hz;
|
||||
}
|
||||
|
||||
/* Get pll rate by id */
|
||||
static uint32_t rkclk_pll_get_rate(struct rk3188_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 rk3188_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) {
|
||||
case APLL_MODE_SLOW:
|
||||
return OSC_HZ;
|
||||
case APLL_MODE_NORMAL:
|
||||
/* normal mode */
|
||||
con = readl(&pll->con0);
|
||||
no = ((con >> CLKOD_SHIFT) & CLKOD_MASK) + 1;
|
||||
nr = ((con >> CLKR_SHIFT) & CLKR_MASK) + 1;
|
||||
con = readl(&pll->con1);
|
||||
nf = ((con >> CLKF_SHIFT) & CLKF_MASK) + 1;
|
||||
|
||||
return (24 * nf / (nr * no)) * 1000000;
|
||||
case APLL_MODE_DEEP:
|
||||
default:
|
||||
return 32768;
|
||||
}
|
||||
}
|
||||
|
||||
static ulong rockchip_mmc_get_clk(struct rk3188_cru *cru, uint gclk_rate,
|
||||
int periph)
|
||||
{
|
||||
uint div;
|
||||
u32 con;
|
||||
|
||||
switch (periph) {
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
con = readl(&cru->cru_clksel_con[12]);
|
||||
div = (con >> EMMC_DIV_SHIFT) & EMMC_DIV_MASK;
|
||||
break;
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
con = readl(&cru->cru_clksel_con[11]);
|
||||
div = (con >> MMC0_DIV_SHIFT) & MMC0_DIV_MASK;
|
||||
break;
|
||||
case HCLK_SDIO:
|
||||
case SCLK_SDIO:
|
||||
con = readl(&cru->cru_clksel_con[12]);
|
||||
div = (con >> SDIO_DIV_SHIFT) & SDIO_DIV_MASK;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return DIV_TO_RATE(gclk_rate, div) / 2;
|
||||
}
|
||||
|
||||
static ulong rockchip_mmc_set_clk(struct rk3188_cru *cru, uint gclk_rate,
|
||||
int periph, uint freq)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
debug("%s: gclk_rate=%u\n", __func__, gclk_rate);
|
||||
/* mmc clock defaulg div 2 internal, need provide double in cru */
|
||||
src_clk_div = DIV_ROUND_UP(gclk_rate / 2, freq) - 1;
|
||||
assert(src_clk_div <= 0x3f);
|
||||
|
||||
switch (periph) {
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[12],
|
||||
EMMC_DIV_MASK << EMMC_DIV_SHIFT,
|
||||
src_clk_div << EMMC_DIV_SHIFT);
|
||||
break;
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[11],
|
||||
MMC0_DIV_MASK << MMC0_DIV_SHIFT,
|
||||
src_clk_div << MMC0_DIV_SHIFT);
|
||||
break;
|
||||
case HCLK_SDIO:
|
||||
case SCLK_SDIO:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[12],
|
||||
SDIO_DIV_MASK << SDIO_DIV_SHIFT,
|
||||
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 rk3188_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;
|
||||
break;
|
||||
case SCLK_SPI1:
|
||||
con = readl(&cru->cru_clksel_con[25]);
|
||||
div = (con >> SPI1_DIV_SHIFT) & SPI1_DIV_MASK;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return DIV_TO_RATE(gclk_rate, div);
|
||||
}
|
||||
|
||||
static ulong rockchip_spi_set_clk(struct rk3188_cru *cru, uint gclk_rate,
|
||||
int periph, uint freq)
|
||||
{
|
||||
int src_clk_div = DIV_ROUND_UP(gclk_rate, freq) - 1;
|
||||
|
||||
assert(src_clk_div < 128);
|
||||
switch (periph) {
|
||||
case SCLK_SPI0:
|
||||
assert(src_clk_div <= SPI0_DIV_MASK);
|
||||
rk_clrsetreg(&cru->cru_clksel_con[25],
|
||||
SPI0_DIV_MASK << SPI0_DIV_SHIFT,
|
||||
src_clk_div << SPI0_DIV_SHIFT);
|
||||
break;
|
||||
case SCLK_SPI1:
|
||||
assert(src_clk_div <= SPI1_DIV_MASK);
|
||||
rk_clrsetreg(&cru->cru_clksel_con[25],
|
||||
SPI1_DIV_MASK << SPI1_DIV_SHIFT,
|
||||
src_clk_div << SPI1_DIV_SHIFT);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return rockchip_spi_get_clk(cru, gclk_rate, periph);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SPL_BUILD
|
||||
static void rkclk_init(struct rk3188_cru *cru, struct rk3188_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 << GPLL_MODE_SHIFT |
|
||||
CPLL_MODE_MASK << CPLL_MODE_SHIFT,
|
||||
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 = DIV_ROUND_UP(GPLL_HZ, CPU_ACLK_HZ) - 1;
|
||||
assert((aclk_div + 1) * CPU_ACLK_HZ == GPLL_HZ && aclk_div <= 0x1f);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[0],
|
||||
CPU_ACLK_PLL_MASK << CPU_ACLK_PLL_SHIFT |
|
||||
A9_CPU_DIV_MASK << A9_CPU_DIV_SHIFT,
|
||||
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 << AHB2APB_DIV_SHIFT |
|
||||
CPU_PCLK_DIV_MASK << CPU_PCLK_DIV_SHIFT |
|
||||
CPU_HCLK_DIV_MASK << CPU_HCLK_DIV_SHIFT,
|
||||
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_PCLK_DIV_SHIFT |
|
||||
PERI_HCLK_DIV_MASK << PERI_HCLK_DIV_SHIFT |
|
||||
PERI_ACLK_DIV_MASK << PERI_ACLK_DIV_SHIFT,
|
||||
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 << GPLL_MODE_SHIFT |
|
||||
CPLL_MODE_MASK << CPLL_MODE_SHIFT,
|
||||
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 rk3188_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
struct rk3188_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:
|
||||
case SCLK_EMMC:
|
||||
case SCLK_SDMMC:
|
||||
case SCLK_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 rk3188_clk_set_rate(struct clk *clk, ulong rate)
|
||||
{
|
||||
struct rk3188_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
struct rk3188_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:
|
||||
case SCLK_EMMC:
|
||||
case SCLK_SDMMC:
|
||||
case SCLK_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 rk3188_clk_ops = {
|
||||
.get_rate = rk3188_clk_get_rate,
|
||||
.set_rate = rk3188_clk_set_rate,
|
||||
};
|
||||
|
||||
static int rk3188_clk_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
#if !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
struct rk3188_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->cru = dev_read_addr_ptr(dev);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3188_clk_probe(struct udevice *dev)
|
||||
{
|
||||
struct rk3188_clk_priv *priv = dev_get_priv(dev);
|
||||
enum rk3188_clk_type type = dev_get_driver_data(dev);
|
||||
|
||||
priv->grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
|
||||
if (IS_ERR(priv->grf))
|
||||
return PTR_ERR(priv->grf);
|
||||
priv->has_bwadj = (type == RK3188A_CRU) ? 1 : 0;
|
||||
|
||||
#ifdef CONFIG_SPL_BUILD
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
struct rk3188_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, priv->has_bwadj);
|
||||
|
||||
/* Init CPU frequency */
|
||||
rkclk_configure_cpu(priv->cru, priv->grf, APLL_HZ, priv->has_bwadj);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3188_clk_bind(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct udevice *sys_child;
|
||||
struct sysreset_reg *priv;
|
||||
|
||||
/* The reset driver does not have a device node, so bind it here */
|
||||
ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
|
||||
&sys_child);
|
||||
if (ret) {
|
||||
debug("Warning: No sysreset driver: ret=%d\n", ret);
|
||||
} else {
|
||||
priv = malloc(sizeof(struct sysreset_reg));
|
||||
priv->glb_srst_fst_value = offsetof(struct rk3188_cru,
|
||||
cru_glb_srst_fst_value);
|
||||
priv->glb_srst_snd_value = offsetof(struct rk3188_cru,
|
||||
cru_glb_srst_snd_value);
|
||||
sys_child->priv = priv;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(RESET_ROCKCHIP)
|
||||
ret = offsetof(struct rk3188_cru, cru_softrst_con[0]);
|
||||
ret = rockchip_reset_bind(dev, ret, 9);
|
||||
if (ret)
|
||||
debug("Warning: software reset driver bind faile\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id rk3188_clk_ids[] = {
|
||||
{ .compatible = "rockchip,rk3188-cru", .data = RK3188_CRU },
|
||||
{ .compatible = "rockchip,rk3188a-cru", .data = RK3188A_CRU },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_rk3188_cru) = {
|
||||
.name = "rockchip_rk3188_cru",
|
||||
.id = UCLASS_CLK,
|
||||
.of_match = rk3188_clk_ids,
|
||||
.priv_auto_alloc_size = sizeof(struct rk3188_clk_priv),
|
||||
.platdata_auto_alloc_size = sizeof(struct rk3188_clk_plat),
|
||||
.ops = &rk3188_clk_ops,
|
||||
.bind = rk3188_clk_bind,
|
||||
.ofdata_to_platdata = rk3188_clk_ofdata_to_platdata,
|
||||
.probe = rk3188_clk_probe,
|
||||
};
|
||||
@@ -0,0 +1,535 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk-uclass.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch-rockchip/clock.h>
|
||||
#include <asm/arch-rockchip/cru_rk322x.h>
|
||||
#include <asm/arch-rockchip/hardware.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dt-bindings/clock/rk3228-cru.h>
|
||||
#include <linux/log2.h>
|
||||
|
||||
enum {
|
||||
VCO_MAX_HZ = 3200U * 1000000,
|
||||
VCO_MIN_HZ = 800 * 1000000,
|
||||
OUTPUT_MAX_HZ = 3200U * 1000000,
|
||||
OUTPUT_MIN_HZ = 24 * 1000000,
|
||||
};
|
||||
|
||||
#define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1))
|
||||
|
||||
#define PLL_DIVISORS(hz, _refdiv, _postdiv1, _postdiv2) {\
|
||||
.refdiv = _refdiv,\
|
||||
.fbdiv = (u32)((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ), \
|
||||
.postdiv1 = _postdiv1, .postdiv2 = _postdiv2};\
|
||||
_Static_assert(((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ) * \
|
||||
OSC_HZ / (_refdiv * _postdiv1 * _postdiv2) == hz, \
|
||||
#hz "Hz cannot be hit with PLL "\
|
||||
"divisors on line " __stringify(__LINE__));
|
||||
|
||||
/* use integer mode*/
|
||||
static const struct pll_div apll_init_cfg = PLL_DIVISORS(APLL_HZ, 1, 3, 1);
|
||||
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2, 1);
|
||||
|
||||
static int rkclk_set_pll(struct rk322x_cru *cru, enum rk_clk_id clk_id,
|
||||
const struct pll_div *div)
|
||||
{
|
||||
int pll_id = rk_pll_id(clk_id);
|
||||
struct rk322x_pll *pll = &cru->pll[pll_id];
|
||||
|
||||
/* All PLLs have same VCO and output frequency range restrictions. */
|
||||
uint vco_hz = OSC_HZ / 1000 * div->fbdiv / div->refdiv * 1000;
|
||||
uint output_hz = vco_hz / div->postdiv1 / div->postdiv2;
|
||||
|
||||
debug("PLL at %p: fb=%d, ref=%d, pst1=%d, pst2=%d, vco=%u Hz, output=%u Hz\n",
|
||||
pll, div->fbdiv, div->refdiv, div->postdiv1,
|
||||
div->postdiv2, vco_hz, output_hz);
|
||||
assert(vco_hz >= VCO_MIN_HZ && vco_hz <= VCO_MAX_HZ &&
|
||||
output_hz >= OUTPUT_MIN_HZ && output_hz <= OUTPUT_MAX_HZ);
|
||||
|
||||
/* use integer mode */
|
||||
rk_setreg(&pll->con1, 1 << PLL_DSMPD_SHIFT);
|
||||
/* Power down */
|
||||
rk_setreg(&pll->con1, 1 << PLL_PD_SHIFT);
|
||||
|
||||
rk_clrsetreg(&pll->con0,
|
||||
PLL_POSTDIV1_MASK | PLL_FBDIV_MASK,
|
||||
(div->postdiv1 << PLL_POSTDIV1_SHIFT) | div->fbdiv);
|
||||
rk_clrsetreg(&pll->con1, PLL_POSTDIV2_MASK | PLL_REFDIV_MASK,
|
||||
(div->postdiv2 << PLL_POSTDIV2_SHIFT |
|
||||
div->refdiv << PLL_REFDIV_SHIFT));
|
||||
|
||||
/* Power Up */
|
||||
rk_clrreg(&pll->con1, 1 << PLL_PD_SHIFT);
|
||||
|
||||
/* waiting for pll lock */
|
||||
while (readl(&pll->con1) & (1 << PLL_LOCK_STATUS_SHIFT))
|
||||
udelay(1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rkclk_init(struct rk322x_cru *cru)
|
||||
{
|
||||
u32 aclk_div;
|
||||
u32 hclk_div;
|
||||
u32 pclk_div;
|
||||
|
||||
/* pll enter slow-mode */
|
||||
rk_clrsetreg(&cru->cru_mode_con,
|
||||
GPLL_MODE_MASK | APLL_MODE_MASK,
|
||||
GPLL_MODE_SLOW << GPLL_MODE_SHIFT |
|
||||
APLL_MODE_SLOW << APLL_MODE_SHIFT);
|
||||
|
||||
/* init pll */
|
||||
rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg);
|
||||
rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);
|
||||
|
||||
/*
|
||||
* select apll as cpu/core clock pll source and
|
||||
* set up dependent divisors for PERI and ACLK clocks.
|
||||
* core hz : apll = 1:1
|
||||
*/
|
||||
aclk_div = APLL_HZ / CORE_ACLK_HZ - 1;
|
||||
assert((aclk_div + 1) * CORE_ACLK_HZ == APLL_HZ && aclk_div < 0x7);
|
||||
|
||||
pclk_div = APLL_HZ / CORE_PERI_HZ - 1;
|
||||
assert((pclk_div + 1) * CORE_PERI_HZ == APLL_HZ && pclk_div < 0xf);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[0],
|
||||
CORE_CLK_PLL_SEL_MASK | CORE_DIV_CON_MASK,
|
||||
CORE_CLK_PLL_SEL_APLL << CORE_CLK_PLL_SEL_SHIFT |
|
||||
0 << CORE_DIV_CON_SHIFT);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[1],
|
||||
CORE_ACLK_DIV_MASK | CORE_PERI_DIV_MASK,
|
||||
aclk_div << CORE_ACLK_DIV_SHIFT |
|
||||
pclk_div << CORE_PERI_DIV_SHIFT);
|
||||
|
||||
/*
|
||||
* select gpll as pd_bus bus clock source and
|
||||
* set up dependent divisors for PCLK/HCLK and ACLK clocks.
|
||||
*/
|
||||
aclk_div = GPLL_HZ / BUS_ACLK_HZ - 1;
|
||||
assert((aclk_div + 1) * BUS_ACLK_HZ == GPLL_HZ && aclk_div <= 0x1f);
|
||||
|
||||
pclk_div = BUS_ACLK_HZ / BUS_PCLK_HZ - 1;
|
||||
assert((pclk_div + 1) * BUS_PCLK_HZ == BUS_ACLK_HZ && pclk_div <= 0x7);
|
||||
|
||||
hclk_div = BUS_ACLK_HZ / BUS_HCLK_HZ - 1;
|
||||
assert((hclk_div + 1) * BUS_HCLK_HZ == BUS_ACLK_HZ && hclk_div <= 0x3);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[0],
|
||||
BUS_ACLK_PLL_SEL_MASK | BUS_ACLK_DIV_MASK,
|
||||
BUS_ACLK_PLL_SEL_GPLL << BUS_ACLK_PLL_SEL_SHIFT |
|
||||
aclk_div << BUS_ACLK_DIV_SHIFT);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[1],
|
||||
BUS_PCLK_DIV_MASK | BUS_HCLK_DIV_MASK,
|
||||
pclk_div << BUS_PCLK_DIV_SHIFT |
|
||||
hclk_div << BUS_HCLK_DIV_SHIFT);
|
||||
|
||||
/*
|
||||
* select gpll as pd_peri bus clock source 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 < 0x8);
|
||||
|
||||
rk_clrsetreg(&cru->cru_clksel_con[10],
|
||||
PERI_PLL_SEL_MASK | PERI_PCLK_DIV_MASK |
|
||||
PERI_HCLK_DIV_MASK | PERI_ACLK_DIV_MASK,
|
||||
PERI_PLL_GPLL << PERI_PLL_SEL_SHIFT |
|
||||
pclk_div << PERI_PCLK_DIV_SHIFT |
|
||||
hclk_div << PERI_HCLK_DIV_SHIFT |
|
||||
aclk_div << PERI_ACLK_DIV_SHIFT);
|
||||
|
||||
/* PLL enter normal-mode */
|
||||
rk_clrsetreg(&cru->cru_mode_con,
|
||||
GPLL_MODE_MASK | APLL_MODE_MASK,
|
||||
GPLL_MODE_NORM << GPLL_MODE_SHIFT |
|
||||
APLL_MODE_NORM << APLL_MODE_SHIFT);
|
||||
}
|
||||
|
||||
/* Get pll rate by id */
|
||||
static uint32_t rkclk_pll_get_rate(struct rk322x_cru *cru,
|
||||
enum rk_clk_id clk_id)
|
||||
{
|
||||
uint32_t refdiv, fbdiv, postdiv1, postdiv2;
|
||||
uint32_t con;
|
||||
int pll_id = rk_pll_id(clk_id);
|
||||
struct rk322x_pll *pll = &cru->pll[pll_id];
|
||||
static u8 clk_shift[CLK_COUNT] = {
|
||||
0xff, APLL_MODE_SHIFT, DPLL_MODE_SHIFT, 0xff,
|
||||
GPLL_MODE_SHIFT, 0xff
|
||||
};
|
||||
static u32 clk_mask[CLK_COUNT] = {
|
||||
0xff, APLL_MODE_MASK, DPLL_MODE_MASK, 0xff,
|
||||
GPLL_MODE_MASK, 0xff
|
||||
};
|
||||
uint shift;
|
||||
uint mask;
|
||||
|
||||
con = readl(&cru->cru_mode_con);
|
||||
shift = clk_shift[clk_id];
|
||||
mask = clk_mask[clk_id];
|
||||
|
||||
switch ((con & mask) >> shift) {
|
||||
case GPLL_MODE_SLOW:
|
||||
return OSC_HZ;
|
||||
case GPLL_MODE_NORM:
|
||||
|
||||
/* normal mode */
|
||||
con = readl(&pll->con0);
|
||||
postdiv1 = (con & PLL_POSTDIV1_MASK) >> PLL_POSTDIV1_SHIFT;
|
||||
fbdiv = (con & PLL_FBDIV_MASK) >> PLL_FBDIV_SHIFT;
|
||||
con = readl(&pll->con1);
|
||||
postdiv2 = (con & PLL_POSTDIV2_MASK) >> PLL_POSTDIV2_SHIFT;
|
||||
refdiv = (con & PLL_REFDIV_MASK) >> PLL_REFDIV_SHIFT;
|
||||
return (24 * fbdiv / (refdiv * postdiv1 * postdiv2)) * 1000000;
|
||||
default:
|
||||
return 32768;
|
||||
}
|
||||
}
|
||||
|
||||
static ulong rockchip_mmc_get_clk(struct rk322x_cru *cru, uint clk_general_rate,
|
||||
int periph)
|
||||
{
|
||||
uint src_rate;
|
||||
uint div, mux;
|
||||
u32 con;
|
||||
|
||||
switch (periph) {
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
case SCLK_EMMC_SAMPLE:
|
||||
con = readl(&cru->cru_clksel_con[11]);
|
||||
mux = (con & EMMC_PLL_MASK) >> EMMC_PLL_SHIFT;
|
||||
con = readl(&cru->cru_clksel_con[12]);
|
||||
div = (con & EMMC_DIV_MASK) >> EMMC_DIV_SHIFT;
|
||||
break;
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
con = readl(&cru->cru_clksel_con[11]);
|
||||
mux = (con & MMC0_PLL_MASK) >> MMC0_PLL_SHIFT;
|
||||
div = (con & MMC0_DIV_MASK) >> MMC0_DIV_SHIFT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
src_rate = mux == EMMC_SEL_24M ? OSC_HZ : clk_general_rate;
|
||||
return DIV_TO_RATE(src_rate, div) / 2;
|
||||
}
|
||||
|
||||
static ulong rk322x_mac_set_clk(struct rk322x_cru *cru, uint freq)
|
||||
{
|
||||
ulong ret;
|
||||
|
||||
/*
|
||||
* The gmac clock can be derived either from an external clock
|
||||
* or can be generated from internally by a divider from SCLK_MAC.
|
||||
*/
|
||||
if (readl(&cru->cru_clksel_con[5]) & BIT(5)) {
|
||||
/* An external clock will always generate the right rate... */
|
||||
ret = freq;
|
||||
} else {
|
||||
u32 con = readl(&cru->cru_clksel_con[5]);
|
||||
ulong pll_rate;
|
||||
u8 div;
|
||||
|
||||
if ((con >> MAC_PLL_SEL_SHIFT) & MAC_PLL_SEL_MASK)
|
||||
pll_rate = GPLL_HZ;
|
||||
else
|
||||
/* CPLL is not set */
|
||||
return -EPERM;
|
||||
|
||||
div = DIV_ROUND_UP(pll_rate, freq) - 1;
|
||||
if (div <= 0x1f)
|
||||
rk_clrsetreg(&cru->cru_clksel_con[5], CLK_MAC_DIV_MASK,
|
||||
div << CLK_MAC_DIV_SHIFT);
|
||||
else
|
||||
debug("Unsupported div for gmac:%d\n", div);
|
||||
|
||||
return DIV_TO_RATE(pll_rate, div);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ulong rockchip_mmc_set_clk(struct rk322x_cru *cru, uint clk_general_rate,
|
||||
int periph, uint freq)
|
||||
{
|
||||
int src_clk_div;
|
||||
int mux;
|
||||
|
||||
debug("%s: clk_general_rate=%u\n", __func__, clk_general_rate);
|
||||
|
||||
/* mmc clock defaulg div 2 internal, need provide double in cru */
|
||||
src_clk_div = DIV_ROUND_UP(clk_general_rate / 2, freq);
|
||||
|
||||
if (src_clk_div > 128) {
|
||||
src_clk_div = DIV_ROUND_UP(OSC_HZ / 2, freq);
|
||||
assert(src_clk_div - 1 < 128);
|
||||
mux = EMMC_SEL_24M;
|
||||
} else {
|
||||
mux = EMMC_SEL_GPLL;
|
||||
}
|
||||
|
||||
switch (periph) {
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
case SCLK_EMMC_SAMPLE:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[11],
|
||||
EMMC_PLL_MASK,
|
||||
mux << EMMC_PLL_SHIFT);
|
||||
rk_clrsetreg(&cru->cru_clksel_con[12],
|
||||
EMMC_DIV_MASK,
|
||||
(src_clk_div - 1) << EMMC_DIV_SHIFT);
|
||||
break;
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
rk_clrsetreg(&cru->cru_clksel_con[11],
|
||||
MMC0_PLL_MASK | MMC0_DIV_MASK,
|
||||
mux << MMC0_PLL_SHIFT |
|
||||
(src_clk_div - 1) << MMC0_DIV_SHIFT);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return rockchip_mmc_get_clk(cru, clk_general_rate, periph);
|
||||
}
|
||||
|
||||
static int rk322x_ddr_set_clk(struct rk322x_cru *cru, unsigned int set_rate)
|
||||
{
|
||||
struct pll_div dpll_cfg;
|
||||
|
||||
/* clk_ddrc == DPLL = 24MHz / refdiv * fbdiv / postdiv1 / postdiv2 */
|
||||
switch (set_rate) {
|
||||
case 400*MHz:
|
||||
dpll_cfg = (struct pll_div)
|
||||
{.refdiv = 1, .fbdiv = 50, .postdiv1 = 3, .postdiv2 = 1};
|
||||
break;
|
||||
case 600*MHz:
|
||||
dpll_cfg = (struct pll_div)
|
||||
{.refdiv = 1, .fbdiv = 75, .postdiv1 = 3, .postdiv2 = 1};
|
||||
break;
|
||||
case 800*MHz:
|
||||
dpll_cfg = (struct pll_div)
|
||||
{.refdiv = 1, .fbdiv = 100, .postdiv1 = 3, .postdiv2 = 1};
|
||||
break;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
/* PLL enter normal-mode */
|
||||
rk_clrsetreg(&cru->cru_mode_con, DPLL_MODE_MASK,
|
||||
DPLL_MODE_NORM << DPLL_MODE_SHIFT);
|
||||
|
||||
return set_rate;
|
||||
}
|
||||
static ulong rk322x_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
struct rk322x_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
ulong rate, gclk_rate;
|
||||
|
||||
gclk_rate = rkclk_pll_get_rate(priv->cru, CLK_GENERAL);
|
||||
switch (clk->id) {
|
||||
case 0 ... 63:
|
||||
rate = rkclk_pll_get_rate(priv->cru, clk->id);
|
||||
break;
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
rate = rockchip_mmc_get_clk(priv->cru, gclk_rate, clk->id);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
static ulong rk322x_clk_set_rate(struct clk *clk, ulong rate)
|
||||
{
|
||||
struct rk322x_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 HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
new_rate = rockchip_mmc_set_clk(priv->cru, gclk_rate,
|
||||
clk->id, rate);
|
||||
break;
|
||||
case CLK_DDR:
|
||||
new_rate = rk322x_ddr_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case SCLK_MAC:
|
||||
new_rate = rk322x_mac_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case PLL_GPLL:
|
||||
return 0;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return new_rate;
|
||||
}
|
||||
|
||||
static int rk322x_gmac_set_parent(struct clk *clk, struct clk *parent)
|
||||
{
|
||||
struct rk322x_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
struct rk322x_cru *cru = priv->cru;
|
||||
|
||||
/*
|
||||
* If the requested parent is in the same clock-controller and the id
|
||||
* is SCLK_MAC_SRC ("sclk_gmac_src"), switch to the internal clock.
|
||||
*/
|
||||
if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC_SRC)) {
|
||||
debug("%s: switching RGMII to SCLK_MAC_SRC\n", __func__);
|
||||
rk_clrsetreg(&cru->cru_clksel_con[5], BIT(5), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the requested parent is in the same clock-controller and the id
|
||||
* is SCLK_MAC_EXTCLK (sclk_mac_extclk), switch to the external clock.
|
||||
*/
|
||||
if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC_EXTCLK)) {
|
||||
debug("%s: switching RGMII to SCLK_MAC_EXTCLK\n", __func__);
|
||||
rk_clrsetreg(&cru->cru_clksel_con[5], BIT(5), BIT(5));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int rk322x_gmac_extclk_set_parent(struct clk *clk, struct clk *parent)
|
||||
{
|
||||
struct rk322x_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
const char *clock_output_name;
|
||||
struct rk322x_cru *cru = priv->cru;
|
||||
int ret;
|
||||
|
||||
ret = dev_read_string_index(parent->dev, "clock-output-names",
|
||||
parent->id, &clock_output_name);
|
||||
if (ret < 0)
|
||||
return -ENODATA;
|
||||
|
||||
if (!strcmp(clock_output_name, "ext_gmac")) {
|
||||
debug("%s: switching gmac extclk to ext_gmac\n", __func__);
|
||||
rk_clrsetreg(&cru->cru_clksel_con[29], BIT(10), 0);
|
||||
return 0;
|
||||
} else if (!strcmp(clock_output_name, "phy_50m_out")) {
|
||||
debug("%s: switching gmac extclk to phy_50m_out\n", __func__);
|
||||
rk_clrsetreg(&cru->cru_clksel_con[29], BIT(10), BIT(10));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int rk322x_clk_set_parent(struct clk *clk, struct clk *parent)
|
||||
{
|
||||
switch (clk->id) {
|
||||
case SCLK_MAC:
|
||||
return rk322x_gmac_set_parent(clk, parent);
|
||||
case SCLK_MAC_EXTCLK:
|
||||
return rk322x_gmac_extclk_set_parent(clk, parent);
|
||||
}
|
||||
|
||||
debug("%s: unsupported clk %ld\n", __func__, clk->id);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static struct clk_ops rk322x_clk_ops = {
|
||||
.get_rate = rk322x_clk_get_rate,
|
||||
.set_rate = rk322x_clk_set_rate,
|
||||
.set_parent = rk322x_clk_set_parent,
|
||||
};
|
||||
|
||||
static int rk322x_clk_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct rk322x_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->cru = dev_read_addr_ptr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk322x_clk_probe(struct udevice *dev)
|
||||
{
|
||||
struct rk322x_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
rkclk_init(priv->cru);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk322x_clk_bind(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct udevice *sys_child;
|
||||
struct sysreset_reg *priv;
|
||||
|
||||
/* The reset driver does not have a device node, so bind it here */
|
||||
ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
|
||||
&sys_child);
|
||||
if (ret) {
|
||||
debug("Warning: No sysreset driver: ret=%d\n", ret);
|
||||
} else {
|
||||
priv = malloc(sizeof(struct sysreset_reg));
|
||||
priv->glb_srst_fst_value = offsetof(struct rk322x_cru,
|
||||
cru_glb_srst_fst_value);
|
||||
priv->glb_srst_snd_value = offsetof(struct rk322x_cru,
|
||||
cru_glb_srst_snd_value);
|
||||
sys_child->priv = priv;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(RESET_ROCKCHIP)
|
||||
ret = offsetof(struct rk322x_cru, cru_softrst_con[0]);
|
||||
ret = rockchip_reset_bind(dev, ret, 9);
|
||||
if (ret)
|
||||
debug("Warning: software reset driver bind faile\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id rk322x_clk_ids[] = {
|
||||
{ .compatible = "rockchip,rk3228-cru" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_rk322x_cru) = {
|
||||
.name = "clk_rk322x",
|
||||
.id = UCLASS_CLK,
|
||||
.of_match = rk322x_clk_ids,
|
||||
.priv_auto_alloc_size = sizeof(struct rk322x_clk_priv),
|
||||
.ofdata_to_platdata = rk322x_clk_ofdata_to_platdata,
|
||||
.ops = &rk322x_clk_ops,
|
||||
.bind = rk322x_clk_bind,
|
||||
.probe = rk322x_clk_probe,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,818 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <bitfield.h>
|
||||
#include <clk-uclass.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/arch-rockchip/clock.h>
|
||||
#include <asm/arch-rockchip/cru_rk3328.h>
|
||||
#include <asm/arch-rockchip/hardware.h>
|
||||
#include <asm/arch-rockchip/grf_rk3328.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dt-bindings/clock/rk3328-cru.h>
|
||||
|
||||
struct pll_div {
|
||||
u32 refdiv;
|
||||
u32 fbdiv;
|
||||
u32 postdiv1;
|
||||
u32 postdiv2;
|
||||
u32 frac;
|
||||
};
|
||||
|
||||
#define RATE_TO_DIV(input_rate, output_rate) \
|
||||
((input_rate) / (output_rate) - 1);
|
||||
#define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1))
|
||||
|
||||
#define PLL_DIVISORS(hz, _refdiv, _postdiv1, _postdiv2) {\
|
||||
.refdiv = _refdiv,\
|
||||
.fbdiv = (u32)((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ),\
|
||||
.postdiv1 = _postdiv1, .postdiv2 = _postdiv2};
|
||||
|
||||
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 1, 4, 1);
|
||||
static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 2, 2, 1);
|
||||
|
||||
static const struct pll_div apll_816_cfg = PLL_DIVISORS(816 * MHz, 1, 2, 1);
|
||||
static const struct pll_div apll_600_cfg = PLL_DIVISORS(600 * MHz, 1, 3, 1);
|
||||
|
||||
static const struct pll_div *apll_cfgs[] = {
|
||||
[APLL_816_MHZ] = &apll_816_cfg,
|
||||
[APLL_600_MHZ] = &apll_600_cfg,
|
||||
};
|
||||
|
||||
enum {
|
||||
/* PLL_CON0 */
|
||||
PLL_POSTDIV1_SHIFT = 12,
|
||||
PLL_POSTDIV1_MASK = 0x7 << PLL_POSTDIV1_SHIFT,
|
||||
PLL_FBDIV_SHIFT = 0,
|
||||
PLL_FBDIV_MASK = 0xfff,
|
||||
|
||||
/* PLL_CON1 */
|
||||
PLL_DSMPD_SHIFT = 12,
|
||||
PLL_DSMPD_MASK = 1 << PLL_DSMPD_SHIFT,
|
||||
PLL_INTEGER_MODE = 1,
|
||||
PLL_LOCK_STATUS_SHIFT = 10,
|
||||
PLL_LOCK_STATUS_MASK = 1 << PLL_LOCK_STATUS_SHIFT,
|
||||
PLL_POSTDIV2_SHIFT = 6,
|
||||
PLL_POSTDIV2_MASK = 0x7 << PLL_POSTDIV2_SHIFT,
|
||||
PLL_REFDIV_SHIFT = 0,
|
||||
PLL_REFDIV_MASK = 0x3f,
|
||||
|
||||
/* PLL_CON2 */
|
||||
PLL_FRACDIV_SHIFT = 0,
|
||||
PLL_FRACDIV_MASK = 0xffffff,
|
||||
|
||||
/* MODE_CON */
|
||||
APLL_MODE_SHIFT = 0,
|
||||
NPLL_MODE_SHIFT = 1,
|
||||
DPLL_MODE_SHIFT = 4,
|
||||
CPLL_MODE_SHIFT = 8,
|
||||
GPLL_MODE_SHIFT = 12,
|
||||
PLL_MODE_SLOW = 0,
|
||||
PLL_MODE_NORM,
|
||||
|
||||
/* CLKSEL_CON0 */
|
||||
CLK_CORE_PLL_SEL_APLL = 0,
|
||||
CLK_CORE_PLL_SEL_GPLL,
|
||||
CLK_CORE_PLL_SEL_DPLL,
|
||||
CLK_CORE_PLL_SEL_NPLL,
|
||||
CLK_CORE_PLL_SEL_SHIFT = 6,
|
||||
CLK_CORE_PLL_SEL_MASK = 3 << CLK_CORE_PLL_SEL_SHIFT,
|
||||
CLK_CORE_DIV_SHIFT = 0,
|
||||
CLK_CORE_DIV_MASK = 0x1f,
|
||||
|
||||
/* CLKSEL_CON1 */
|
||||
ACLKM_CORE_DIV_SHIFT = 4,
|
||||
ACLKM_CORE_DIV_MASK = 0x7 << ACLKM_CORE_DIV_SHIFT,
|
||||
PCLK_DBG_DIV_SHIFT = 0,
|
||||
PCLK_DBG_DIV_MASK = 0xF << PCLK_DBG_DIV_SHIFT,
|
||||
|
||||
/* CLKSEL_CON27 */
|
||||
GMAC2IO_PLL_SEL_SHIFT = 7,
|
||||
GMAC2IO_PLL_SEL_MASK = 1 << GMAC2IO_PLL_SEL_SHIFT,
|
||||
GMAC2IO_PLL_SEL_CPLL = 0,
|
||||
GMAC2IO_PLL_SEL_GPLL = 1,
|
||||
GMAC2IO_CLK_DIV_MASK = 0x1f,
|
||||
GMAC2IO_CLK_DIV_SHIFT = 0,
|
||||
|
||||
/* CLKSEL_CON28 */
|
||||
ACLK_PERIHP_PLL_SEL_CPLL = 0,
|
||||
ACLK_PERIHP_PLL_SEL_GPLL,
|
||||
ACLK_PERIHP_PLL_SEL_HDMIPHY,
|
||||
ACLK_PERIHP_PLL_SEL_SHIFT = 6,
|
||||
ACLK_PERIHP_PLL_SEL_MASK = 3 << ACLK_PERIHP_PLL_SEL_SHIFT,
|
||||
ACLK_PERIHP_DIV_CON_SHIFT = 0,
|
||||
ACLK_PERIHP_DIV_CON_MASK = 0x1f,
|
||||
|
||||
/* CLKSEL_CON29 */
|
||||
PCLK_PERIHP_DIV_CON_SHIFT = 4,
|
||||
PCLK_PERIHP_DIV_CON_MASK = 0x7 << PCLK_PERIHP_DIV_CON_SHIFT,
|
||||
HCLK_PERIHP_DIV_CON_SHIFT = 0,
|
||||
HCLK_PERIHP_DIV_CON_MASK = 3 << HCLK_PERIHP_DIV_CON_SHIFT,
|
||||
|
||||
/* CLKSEL_CON22 */
|
||||
CLK_TSADC_DIV_CON_SHIFT = 0,
|
||||
CLK_TSADC_DIV_CON_MASK = 0x3ff,
|
||||
|
||||
/* CLKSEL_CON23 */
|
||||
CLK_SARADC_DIV_CON_SHIFT = 0,
|
||||
CLK_SARADC_DIV_CON_MASK = GENMASK(9, 0),
|
||||
CLK_SARADC_DIV_CON_WIDTH = 10,
|
||||
|
||||
/* CLKSEL_CON24 */
|
||||
CLK_PWM_PLL_SEL_CPLL = 0,
|
||||
CLK_PWM_PLL_SEL_GPLL,
|
||||
CLK_PWM_PLL_SEL_SHIFT = 15,
|
||||
CLK_PWM_PLL_SEL_MASK = 1 << CLK_PWM_PLL_SEL_SHIFT,
|
||||
CLK_PWM_DIV_CON_SHIFT = 8,
|
||||
CLK_PWM_DIV_CON_MASK = 0x7f << CLK_PWM_DIV_CON_SHIFT,
|
||||
|
||||
CLK_SPI_PLL_SEL_CPLL = 0,
|
||||
CLK_SPI_PLL_SEL_GPLL,
|
||||
CLK_SPI_PLL_SEL_SHIFT = 7,
|
||||
CLK_SPI_PLL_SEL_MASK = 1 << CLK_SPI_PLL_SEL_SHIFT,
|
||||
CLK_SPI_DIV_CON_SHIFT = 0,
|
||||
CLK_SPI_DIV_CON_MASK = 0x7f << CLK_SPI_DIV_CON_SHIFT,
|
||||
|
||||
/* CLKSEL_CON30 */
|
||||
CLK_SDMMC_PLL_SEL_CPLL = 0,
|
||||
CLK_SDMMC_PLL_SEL_GPLL,
|
||||
CLK_SDMMC_PLL_SEL_24M,
|
||||
CLK_SDMMC_PLL_SEL_USBPHY,
|
||||
CLK_SDMMC_PLL_SHIFT = 8,
|
||||
CLK_SDMMC_PLL_MASK = 0x3 << CLK_SDMMC_PLL_SHIFT,
|
||||
CLK_SDMMC_DIV_CON_SHIFT = 0,
|
||||
CLK_SDMMC_DIV_CON_MASK = 0xff << CLK_SDMMC_DIV_CON_SHIFT,
|
||||
|
||||
/* CLKSEL_CON32 */
|
||||
CLK_EMMC_PLL_SEL_CPLL = 0,
|
||||
CLK_EMMC_PLL_SEL_GPLL,
|
||||
CLK_EMMC_PLL_SEL_24M,
|
||||
CLK_EMMC_PLL_SEL_USBPHY,
|
||||
CLK_EMMC_PLL_SHIFT = 8,
|
||||
CLK_EMMC_PLL_MASK = 0x3 << CLK_EMMC_PLL_SHIFT,
|
||||
CLK_EMMC_DIV_CON_SHIFT = 0,
|
||||
CLK_EMMC_DIV_CON_MASK = 0xff << CLK_EMMC_DIV_CON_SHIFT,
|
||||
|
||||
/* CLKSEL_CON34 */
|
||||
CLK_I2C_PLL_SEL_CPLL = 0,
|
||||
CLK_I2C_PLL_SEL_GPLL,
|
||||
CLK_I2C_DIV_CON_MASK = 0x7f,
|
||||
CLK_I2C_PLL_SEL_MASK = 1,
|
||||
CLK_I2C1_PLL_SEL_SHIFT = 15,
|
||||
CLK_I2C1_DIV_CON_SHIFT = 8,
|
||||
CLK_I2C0_PLL_SEL_SHIFT = 7,
|
||||
CLK_I2C0_DIV_CON_SHIFT = 0,
|
||||
|
||||
/* CLKSEL_CON35 */
|
||||
CLK_I2C3_PLL_SEL_SHIFT = 15,
|
||||
CLK_I2C3_DIV_CON_SHIFT = 8,
|
||||
CLK_I2C2_PLL_SEL_SHIFT = 7,
|
||||
CLK_I2C2_DIV_CON_SHIFT = 0,
|
||||
};
|
||||
|
||||
#define VCO_MAX_KHZ (3200 * (MHz / KHz))
|
||||
#define VCO_MIN_KHZ (800 * (MHz / KHz))
|
||||
#define OUTPUT_MAX_KHZ (3200 * (MHz / KHz))
|
||||
#define OUTPUT_MIN_KHZ (16 * (MHz / KHz))
|
||||
|
||||
/*
|
||||
* the div restructions of pll in integer mode, these are defined in
|
||||
* * CRU_*PLL_CON0 or PMUCRU_*PLL_CON0
|
||||
*/
|
||||
#define PLL_DIV_MIN 16
|
||||
#define PLL_DIV_MAX 3200
|
||||
|
||||
/*
|
||||
* 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 void rkclk_set_pll(struct rk3328_cru *cru, enum rk_clk_id clk_id,
|
||||
const struct pll_div *div)
|
||||
{
|
||||
u32 *pll_con;
|
||||
u32 mode_shift, mode_mask;
|
||||
|
||||
pll_con = NULL;
|
||||
mode_shift = 0;
|
||||
switch (clk_id) {
|
||||
case CLK_ARM:
|
||||
pll_con = cru->apll_con;
|
||||
mode_shift = APLL_MODE_SHIFT;
|
||||
break;
|
||||
case CLK_DDR:
|
||||
pll_con = cru->dpll_con;
|
||||
mode_shift = DPLL_MODE_SHIFT;
|
||||
break;
|
||||
case CLK_CODEC:
|
||||
pll_con = cru->cpll_con;
|
||||
mode_shift = CPLL_MODE_SHIFT;
|
||||
break;
|
||||
case CLK_GENERAL:
|
||||
pll_con = cru->gpll_con;
|
||||
mode_shift = GPLL_MODE_SHIFT;
|
||||
break;
|
||||
case CLK_NEW:
|
||||
pll_con = cru->npll_con;
|
||||
mode_shift = NPLL_MODE_SHIFT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
mode_mask = 1 << mode_shift;
|
||||
|
||||
/* All 8 PLLs have same VCO and output frequency range restrictions. */
|
||||
u32 vco_khz = OSC_HZ / 1000 * div->fbdiv / div->refdiv;
|
||||
u32 output_khz = vco_khz / div->postdiv1 / div->postdiv2;
|
||||
|
||||
debug("PLL at %p: fbdiv=%d, refdiv=%d, postdiv1=%d, \
|
||||
postdiv2=%d, vco=%u khz, output=%u khz\n",
|
||||
pll_con, div->fbdiv, div->refdiv, div->postdiv1,
|
||||
div->postdiv2, vco_khz, output_khz);
|
||||
assert(vco_khz >= VCO_MIN_KHZ && vco_khz <= VCO_MAX_KHZ &&
|
||||
output_khz >= OUTPUT_MIN_KHZ && output_khz <= OUTPUT_MAX_KHZ &&
|
||||
div->fbdiv >= PLL_DIV_MIN && div->fbdiv <= PLL_DIV_MAX);
|
||||
|
||||
/*
|
||||
* When power on or changing PLL setting,
|
||||
* we must force PLL into slow mode to ensure output stable clock.
|
||||
*/
|
||||
rk_clrsetreg(&cru->mode_con, mode_mask, PLL_MODE_SLOW << mode_shift);
|
||||
|
||||
/* use integer mode */
|
||||
rk_clrsetreg(&pll_con[1], PLL_DSMPD_MASK,
|
||||
PLL_INTEGER_MODE << PLL_DSMPD_SHIFT);
|
||||
|
||||
rk_clrsetreg(&pll_con[0],
|
||||
PLL_FBDIV_MASK | PLL_POSTDIV1_MASK,
|
||||
(div->fbdiv << PLL_FBDIV_SHIFT) |
|
||||
(div->postdiv1 << PLL_POSTDIV1_SHIFT));
|
||||
rk_clrsetreg(&pll_con[1],
|
||||
PLL_POSTDIV2_MASK | PLL_REFDIV_MASK,
|
||||
(div->postdiv2 << PLL_POSTDIV2_SHIFT) |
|
||||
(div->refdiv << PLL_REFDIV_SHIFT));
|
||||
|
||||
/* waiting for pll lock */
|
||||
while (!(readl(&pll_con[1]) & (1 << PLL_LOCK_STATUS_SHIFT)))
|
||||
udelay(1);
|
||||
|
||||
/* pll enter normal mode */
|
||||
rk_clrsetreg(&cru->mode_con, mode_mask, PLL_MODE_NORM << mode_shift);
|
||||
}
|
||||
|
||||
static void rkclk_init(struct rk3328_cru *cru)
|
||||
{
|
||||
u32 aclk_div;
|
||||
u32 hclk_div;
|
||||
u32 pclk_div;
|
||||
|
||||
rk3328_configure_cpu(cru, APLL_600_MHZ);
|
||||
|
||||
/* configure gpll cpll */
|
||||
rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);
|
||||
rkclk_set_pll(cru, CLK_CODEC, &cpll_init_cfg);
|
||||
|
||||
/* configure perihp aclk, hclk, pclk */
|
||||
aclk_div = GPLL_HZ / PERIHP_ACLK_HZ - 1;
|
||||
hclk_div = PERIHP_ACLK_HZ / PERIHP_HCLK_HZ - 1;
|
||||
pclk_div = PERIHP_ACLK_HZ / PERIHP_PCLK_HZ - 1;
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[28],
|
||||
ACLK_PERIHP_PLL_SEL_MASK | ACLK_PERIHP_DIV_CON_MASK,
|
||||
ACLK_PERIHP_PLL_SEL_GPLL << ACLK_PERIHP_PLL_SEL_SHIFT |
|
||||
aclk_div << ACLK_PERIHP_DIV_CON_SHIFT);
|
||||
rk_clrsetreg(&cru->clksel_con[29],
|
||||
PCLK_PERIHP_DIV_CON_MASK | HCLK_PERIHP_DIV_CON_MASK,
|
||||
pclk_div << PCLK_PERIHP_DIV_CON_SHIFT |
|
||||
hclk_div << HCLK_PERIHP_DIV_CON_SHIFT);
|
||||
}
|
||||
|
||||
void rk3328_configure_cpu(struct rk3328_cru *cru,
|
||||
enum apll_frequencies apll_freq)
|
||||
{
|
||||
u32 clk_core_div;
|
||||
u32 aclkm_div;
|
||||
u32 pclk_dbg_div;
|
||||
|
||||
rkclk_set_pll(cru, CLK_ARM, apll_cfgs[apll_freq]);
|
||||
|
||||
clk_core_div = APLL_HZ / CLK_CORE_HZ - 1;
|
||||
aclkm_div = APLL_HZ / ACLKM_CORE_HZ / (clk_core_div + 1) - 1;
|
||||
pclk_dbg_div = APLL_HZ / PCLK_DBG_HZ / (clk_core_div + 1) - 1;
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[0],
|
||||
CLK_CORE_PLL_SEL_MASK | CLK_CORE_DIV_MASK,
|
||||
CLK_CORE_PLL_SEL_APLL << CLK_CORE_PLL_SEL_SHIFT |
|
||||
clk_core_div << CLK_CORE_DIV_SHIFT);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[1],
|
||||
PCLK_DBG_DIV_MASK | ACLKM_CORE_DIV_MASK,
|
||||
pclk_dbg_div << PCLK_DBG_DIV_SHIFT |
|
||||
aclkm_div << ACLKM_CORE_DIV_SHIFT);
|
||||
}
|
||||
|
||||
|
||||
static ulong rk3328_i2c_get_clk(struct rk3328_cru *cru, ulong clk_id)
|
||||
{
|
||||
u32 div, con;
|
||||
|
||||
switch (clk_id) {
|
||||
case SCLK_I2C0:
|
||||
con = readl(&cru->clksel_con[34]);
|
||||
div = con >> CLK_I2C0_DIV_CON_SHIFT & CLK_I2C_DIV_CON_MASK;
|
||||
break;
|
||||
case SCLK_I2C1:
|
||||
con = readl(&cru->clksel_con[34]);
|
||||
div = con >> CLK_I2C1_DIV_CON_SHIFT & CLK_I2C_DIV_CON_MASK;
|
||||
break;
|
||||
case SCLK_I2C2:
|
||||
con = readl(&cru->clksel_con[35]);
|
||||
div = con >> CLK_I2C2_DIV_CON_SHIFT & CLK_I2C_DIV_CON_MASK;
|
||||
break;
|
||||
case SCLK_I2C3:
|
||||
con = readl(&cru->clksel_con[35]);
|
||||
div = con >> CLK_I2C3_DIV_CON_SHIFT & CLK_I2C_DIV_CON_MASK;
|
||||
break;
|
||||
default:
|
||||
printf("do not support this i2c bus\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return DIV_TO_RATE(GPLL_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rk3328_i2c_set_clk(struct rk3328_cru *cru, ulong clk_id, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = GPLL_HZ / hz;
|
||||
assert(src_clk_div - 1 < 127);
|
||||
|
||||
switch (clk_id) {
|
||||
case SCLK_I2C0:
|
||||
rk_clrsetreg(&cru->clksel_con[34],
|
||||
CLK_I2C_DIV_CON_MASK << CLK_I2C0_DIV_CON_SHIFT |
|
||||
CLK_I2C_PLL_SEL_MASK << CLK_I2C0_PLL_SEL_SHIFT,
|
||||
(src_clk_div - 1) << CLK_I2C0_DIV_CON_SHIFT |
|
||||
CLK_I2C_PLL_SEL_GPLL << CLK_I2C0_PLL_SEL_SHIFT);
|
||||
break;
|
||||
case SCLK_I2C1:
|
||||
rk_clrsetreg(&cru->clksel_con[34],
|
||||
CLK_I2C_DIV_CON_MASK << CLK_I2C1_DIV_CON_SHIFT |
|
||||
CLK_I2C_PLL_SEL_MASK << CLK_I2C1_PLL_SEL_SHIFT,
|
||||
(src_clk_div - 1) << CLK_I2C1_DIV_CON_SHIFT |
|
||||
CLK_I2C_PLL_SEL_GPLL << CLK_I2C1_PLL_SEL_SHIFT);
|
||||
break;
|
||||
case SCLK_I2C2:
|
||||
rk_clrsetreg(&cru->clksel_con[35],
|
||||
CLK_I2C_DIV_CON_MASK << CLK_I2C2_DIV_CON_SHIFT |
|
||||
CLK_I2C_PLL_SEL_MASK << CLK_I2C2_PLL_SEL_SHIFT,
|
||||
(src_clk_div - 1) << CLK_I2C2_DIV_CON_SHIFT |
|
||||
CLK_I2C_PLL_SEL_GPLL << CLK_I2C2_PLL_SEL_SHIFT);
|
||||
break;
|
||||
case SCLK_I2C3:
|
||||
rk_clrsetreg(&cru->clksel_con[35],
|
||||
CLK_I2C_DIV_CON_MASK << CLK_I2C3_DIV_CON_SHIFT |
|
||||
CLK_I2C_PLL_SEL_MASK << CLK_I2C3_PLL_SEL_SHIFT,
|
||||
(src_clk_div - 1) << CLK_I2C3_DIV_CON_SHIFT |
|
||||
CLK_I2C_PLL_SEL_GPLL << CLK_I2C3_PLL_SEL_SHIFT);
|
||||
break;
|
||||
default:
|
||||
printf("do not support this i2c bus\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return DIV_TO_RATE(GPLL_HZ, src_clk_div);
|
||||
}
|
||||
|
||||
static ulong rk3328_gmac2io_set_clk(struct rk3328_cru *cru, ulong rate)
|
||||
{
|
||||
struct rk3328_grf_regs *grf;
|
||||
ulong ret;
|
||||
|
||||
grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
|
||||
|
||||
/*
|
||||
* The RGMII CLK can be derived either from an external "clkin"
|
||||
* or can be generated from internally by a divider from SCLK_MAC.
|
||||
*/
|
||||
if (readl(&grf->mac_con[1]) & BIT(10) &&
|
||||
readl(&grf->soc_con[4]) & BIT(14)) {
|
||||
/* An external clock will always generate the right rate... */
|
||||
ret = rate;
|
||||
} else {
|
||||
u32 con = readl(&cru->clksel_con[27]);
|
||||
ulong pll_rate;
|
||||
u8 div;
|
||||
|
||||
if ((con >> GMAC2IO_PLL_SEL_SHIFT) & GMAC2IO_PLL_SEL_GPLL)
|
||||
pll_rate = GPLL_HZ;
|
||||
else
|
||||
pll_rate = CPLL_HZ;
|
||||
|
||||
div = DIV_ROUND_UP(pll_rate, rate) - 1;
|
||||
if (div <= 0x1f)
|
||||
rk_clrsetreg(&cru->clksel_con[27], GMAC2IO_CLK_DIV_MASK,
|
||||
div << GMAC2IO_CLK_DIV_SHIFT);
|
||||
else
|
||||
debug("Unsupported div for gmac:%d\n", div);
|
||||
|
||||
return DIV_TO_RATE(pll_rate, div);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ulong rk3328_mmc_get_clk(struct rk3328_cru *cru, uint clk_id)
|
||||
{
|
||||
u32 div, con, con_id;
|
||||
|
||||
switch (clk_id) {
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
con_id = 30;
|
||||
break;
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
con_id = 32;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
con = readl(&cru->clksel_con[con_id]);
|
||||
div = (con & CLK_EMMC_DIV_CON_MASK) >> CLK_EMMC_DIV_CON_SHIFT;
|
||||
|
||||
if ((con & CLK_EMMC_PLL_MASK) >> CLK_EMMC_PLL_SHIFT
|
||||
== CLK_EMMC_PLL_SEL_24M)
|
||||
return DIV_TO_RATE(OSC_HZ, div) / 2;
|
||||
else
|
||||
return DIV_TO_RATE(GPLL_HZ, div) / 2;
|
||||
}
|
||||
|
||||
static ulong rk3328_mmc_set_clk(struct rk3328_cru *cru,
|
||||
ulong clk_id, ulong set_rate)
|
||||
{
|
||||
int src_clk_div;
|
||||
u32 con_id;
|
||||
|
||||
switch (clk_id) {
|
||||
case HCLK_SDMMC:
|
||||
case SCLK_SDMMC:
|
||||
con_id = 30;
|
||||
break;
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
con_id = 32;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Select clk_sdmmc/emmc source from GPLL by default */
|
||||
/* mmc clock defaulg div 2 internal, need provide double in cru */
|
||||
src_clk_div = DIV_ROUND_UP(GPLL_HZ / 2, set_rate);
|
||||
|
||||
if (src_clk_div > 127) {
|
||||
/* use 24MHz source for 400KHz clock */
|
||||
src_clk_div = DIV_ROUND_UP(OSC_HZ / 2, set_rate);
|
||||
rk_clrsetreg(&cru->clksel_con[con_id],
|
||||
CLK_EMMC_PLL_MASK | CLK_EMMC_DIV_CON_MASK,
|
||||
CLK_EMMC_PLL_SEL_24M << CLK_EMMC_PLL_SHIFT |
|
||||
(src_clk_div - 1) << CLK_EMMC_DIV_CON_SHIFT);
|
||||
} else {
|
||||
rk_clrsetreg(&cru->clksel_con[con_id],
|
||||
CLK_EMMC_PLL_MASK | CLK_EMMC_DIV_CON_MASK,
|
||||
CLK_EMMC_PLL_SEL_GPLL << CLK_EMMC_PLL_SHIFT |
|
||||
(src_clk_div - 1) << CLK_EMMC_DIV_CON_SHIFT);
|
||||
}
|
||||
|
||||
return rk3328_mmc_get_clk(cru, clk_id);
|
||||
}
|
||||
|
||||
static ulong rk3328_pwm_get_clk(struct rk3328_cru *cru)
|
||||
{
|
||||
u32 div, con;
|
||||
|
||||
con = readl(&cru->clksel_con[24]);
|
||||
div = (con & CLK_PWM_DIV_CON_MASK) >> CLK_PWM_DIV_CON_SHIFT;
|
||||
|
||||
return DIV_TO_RATE(GPLL_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rk3328_pwm_set_clk(struct rk3328_cru *cru, uint hz)
|
||||
{
|
||||
u32 div = GPLL_HZ / hz;
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[24],
|
||||
CLK_PWM_PLL_SEL_MASK | CLK_PWM_DIV_CON_MASK,
|
||||
CLK_PWM_PLL_SEL_GPLL << CLK_PWM_PLL_SEL_SHIFT |
|
||||
(div - 1) << CLK_PWM_DIV_CON_SHIFT);
|
||||
|
||||
return DIV_TO_RATE(GPLL_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rk3328_saradc_get_clk(struct rk3328_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
|
||||
val = readl(&cru->clksel_con[23]);
|
||||
div = bitfield_extract(val, CLK_SARADC_DIV_CON_SHIFT,
|
||||
CLK_SARADC_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(OSC_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rk3328_saradc_set_clk(struct rk3328_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(OSC_HZ, hz) - 1;
|
||||
assert(src_clk_div < 128);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[23],
|
||||
CLK_SARADC_DIV_CON_MASK,
|
||||
src_clk_div << CLK_SARADC_DIV_CON_SHIFT);
|
||||
|
||||
return rk3328_saradc_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rk3328_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
struct rk3328_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
ulong rate = 0;
|
||||
|
||||
switch (clk->id) {
|
||||
case 0 ... 29:
|
||||
return 0;
|
||||
case HCLK_SDMMC:
|
||||
case HCLK_EMMC:
|
||||
case SCLK_SDMMC:
|
||||
case SCLK_EMMC:
|
||||
rate = rk3328_mmc_get_clk(priv->cru, clk->id);
|
||||
break;
|
||||
case SCLK_I2C0:
|
||||
case SCLK_I2C1:
|
||||
case SCLK_I2C2:
|
||||
case SCLK_I2C3:
|
||||
rate = rk3328_i2c_get_clk(priv->cru, clk->id);
|
||||
break;
|
||||
case SCLK_PWM:
|
||||
rate = rk3328_pwm_get_clk(priv->cru);
|
||||
break;
|
||||
case SCLK_SARADC:
|
||||
rate = rk3328_saradc_get_clk(priv->cru);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
static ulong rk3328_clk_set_rate(struct clk *clk, ulong rate)
|
||||
{
|
||||
struct rk3328_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
ulong ret = 0;
|
||||
|
||||
switch (clk->id) {
|
||||
case 0 ... 29:
|
||||
return 0;
|
||||
case HCLK_SDMMC:
|
||||
case HCLK_EMMC:
|
||||
case SCLK_SDMMC:
|
||||
case SCLK_EMMC:
|
||||
ret = rk3328_mmc_set_clk(priv->cru, clk->id, rate);
|
||||
break;
|
||||
case SCLK_I2C0:
|
||||
case SCLK_I2C1:
|
||||
case SCLK_I2C2:
|
||||
case SCLK_I2C3:
|
||||
ret = rk3328_i2c_set_clk(priv->cru, clk->id, rate);
|
||||
break;
|
||||
case SCLK_MAC2IO:
|
||||
ret = rk3328_gmac2io_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case SCLK_PWM:
|
||||
ret = rk3328_pwm_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case SCLK_SARADC:
|
||||
ret = rk3328_saradc_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case DCLK_LCDC:
|
||||
case SCLK_PDM:
|
||||
case SCLK_RTC32K:
|
||||
case SCLK_UART0:
|
||||
case SCLK_UART1:
|
||||
case SCLK_UART2:
|
||||
case SCLK_SDIO:
|
||||
case SCLK_TSP:
|
||||
case SCLK_WIFI:
|
||||
case ACLK_BUS_PRE:
|
||||
case HCLK_BUS_PRE:
|
||||
case PCLK_BUS_PRE:
|
||||
case ACLK_PERI_PRE:
|
||||
case HCLK_PERI:
|
||||
case PCLK_PERI:
|
||||
case ACLK_VIO_PRE:
|
||||
case HCLK_VIO_PRE:
|
||||
case ACLK_RGA_PRE:
|
||||
case SCLK_RGA:
|
||||
case ACLK_VOP_PRE:
|
||||
case ACLK_RKVDEC_PRE:
|
||||
case ACLK_RKVENC:
|
||||
case ACLK_VPU_PRE:
|
||||
case SCLK_VDEC_CABAC:
|
||||
case SCLK_VDEC_CORE:
|
||||
case SCLK_VENC_CORE:
|
||||
case SCLK_VENC_DSP:
|
||||
case SCLK_EFUSE:
|
||||
case PCLK_DDR:
|
||||
case ACLK_GMAC:
|
||||
case PCLK_GMAC:
|
||||
case SCLK_USB3OTG_SUSPEND:
|
||||
return 0;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rk3328_gmac2io_set_parent(struct clk *clk, struct clk *parent)
|
||||
{
|
||||
struct rk3328_grf_regs *grf;
|
||||
const char *clock_output_name;
|
||||
int ret;
|
||||
|
||||
grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
|
||||
|
||||
/*
|
||||
* If the requested parent is in the same clock-controller and the id
|
||||
* is SCLK_MAC2IO_SRC ("clk_mac2io_src"), switch to the internal clock.
|
||||
*/
|
||||
if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC2IO_SRC)) {
|
||||
debug("%s: switching RGMII to SCLK_MAC2IO_SRC\n", __func__);
|
||||
rk_clrreg(&grf->mac_con[1], BIT(10));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, we need to check the clock-output-names of the
|
||||
* requested parent to see if the requested id is "gmac_clkin".
|
||||
*/
|
||||
ret = dev_read_string_index(parent->dev, "clock-output-names",
|
||||
parent->id, &clock_output_name);
|
||||
if (ret < 0)
|
||||
return -ENODATA;
|
||||
|
||||
/* If this is "gmac_clkin", switch to the external clock input */
|
||||
if (!strcmp(clock_output_name, "gmac_clkin")) {
|
||||
debug("%s: switching RGMII to CLKIN\n", __func__);
|
||||
rk_setreg(&grf->mac_con[1], BIT(10));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int rk3328_gmac2io_ext_set_parent(struct clk *clk, struct clk *parent)
|
||||
{
|
||||
struct rk3328_grf_regs *grf;
|
||||
const char *clock_output_name;
|
||||
int ret;
|
||||
|
||||
grf = syscon_get_first_range(ROCKCHIP_SYSCON_GRF);
|
||||
|
||||
/*
|
||||
* If the requested parent is in the same clock-controller and the id
|
||||
* is SCLK_MAC2IO ("clk_mac2io"), switch to the internal clock.
|
||||
*/
|
||||
if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC2IO)) {
|
||||
debug("%s: switching RGMII to SCLK_MAC2IO\n", __func__);
|
||||
rk_clrreg(&grf->soc_con[4], BIT(14));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, we need to check the clock-output-names of the
|
||||
* requested parent to see if the requested id is "gmac_clkin".
|
||||
*/
|
||||
ret = dev_read_string_index(parent->dev, "clock-output-names",
|
||||
parent->id, &clock_output_name);
|
||||
if (ret < 0)
|
||||
return -ENODATA;
|
||||
|
||||
/* If this is "gmac_clkin", switch to the external clock input */
|
||||
if (!strcmp(clock_output_name, "gmac_clkin")) {
|
||||
debug("%s: switching RGMII to CLKIN\n", __func__);
|
||||
rk_setreg(&grf->soc_con[4], BIT(14));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int rk3328_clk_set_parent(struct clk *clk, struct clk *parent)
|
||||
{
|
||||
switch (clk->id) {
|
||||
case SCLK_MAC2IO:
|
||||
return rk3328_gmac2io_set_parent(clk, parent);
|
||||
case SCLK_MAC2IO_EXT:
|
||||
return rk3328_gmac2io_ext_set_parent(clk, parent);
|
||||
case DCLK_LCDC:
|
||||
case SCLK_PDM:
|
||||
case SCLK_RTC32K:
|
||||
case SCLK_UART0:
|
||||
case SCLK_UART1:
|
||||
case SCLK_UART2:
|
||||
return 0;
|
||||
}
|
||||
|
||||
debug("%s: unsupported clk %ld\n", __func__, clk->id);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static struct clk_ops rk3328_clk_ops = {
|
||||
.get_rate = rk3328_clk_get_rate,
|
||||
.set_rate = rk3328_clk_set_rate,
|
||||
.set_parent = rk3328_clk_set_parent,
|
||||
};
|
||||
|
||||
static int rk3328_clk_probe(struct udevice *dev)
|
||||
{
|
||||
struct rk3328_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
rkclk_init(priv->cru);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3328_clk_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct rk3328_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->cru = dev_read_addr_ptr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3328_clk_bind(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct udevice *sys_child;
|
||||
struct sysreset_reg *priv;
|
||||
|
||||
/* The reset driver does not have a device node, so bind it here */
|
||||
ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
|
||||
&sys_child);
|
||||
if (ret) {
|
||||
debug("Warning: No sysreset driver: ret=%d\n", ret);
|
||||
} else {
|
||||
priv = malloc(sizeof(struct sysreset_reg));
|
||||
priv->glb_srst_fst_value = offsetof(struct rk3328_cru,
|
||||
glb_srst_fst_value);
|
||||
priv->glb_srst_snd_value = offsetof(struct rk3328_cru,
|
||||
glb_srst_snd_value);
|
||||
sys_child->priv = priv;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(RESET_ROCKCHIP)
|
||||
ret = offsetof(struct rk3328_cru, softrst_con[0]);
|
||||
ret = rockchip_reset_bind(dev, ret, 12);
|
||||
if (ret)
|
||||
debug("Warning: software reset driver bind faile\n");
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct udevice_id rk3328_clk_ids[] = {
|
||||
{ .compatible = "rockchip,rk3328-cru" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_rk3328_cru) = {
|
||||
.name = "rockchip_rk3328_cru",
|
||||
.id = UCLASS_CLK,
|
||||
.of_match = rk3328_clk_ids,
|
||||
.priv_auto_alloc_size = sizeof(struct rk3328_clk_priv),
|
||||
.ofdata_to_platdata = rk3328_clk_ofdata_to_platdata,
|
||||
.ops = &rk3328_clk_ops,
|
||||
.bind = rk3328_clk_bind,
|
||||
.probe = rk3328_clk_probe,
|
||||
};
|
||||
@@ -0,0 +1,650 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
|
||||
* Author: Andy Yan <andy.yan@rock-chips.com>
|
||||
* (C) Copyright 2017 Theobroma Systems Design und Consulting GmbH
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk-uclass.h>
|
||||
#include <dm.h>
|
||||
#include <dt-structs.h>
|
||||
#include <errno.h>
|
||||
#include <mapmem.h>
|
||||
#include <syscon.h>
|
||||
#include <bitfield.h>
|
||||
#include <asm/arch-rockchip/clock.h>
|
||||
#include <asm/arch-rockchip/cru_rk3368.h>
|
||||
#include <asm/arch-rockchip/hardware.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dt-bindings/clock/rk3368-cru.h>
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
struct rk3368_clk_plat {
|
||||
struct dtd_rockchip_rk3368_cru dtd;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct pll_div {
|
||||
u32 nr;
|
||||
u32 nf;
|
||||
u32 no;
|
||||
};
|
||||
|
||||
#define OSC_HZ (24 * 1000 * 1000)
|
||||
#define APLL_L_HZ (800 * 1000 * 1000)
|
||||
#define APLL_B_HZ (816 * 1000 * 1000)
|
||||
#define GPLL_HZ (576 * 1000 * 1000)
|
||||
#define CPLL_HZ (400 * 1000 * 1000)
|
||||
|
||||
#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__));
|
||||
|
||||
#if IS_ENABLED(CONFIG_SPL_BUILD) || IS_ENABLED(CONFIG_TPL_BUILD)
|
||||
static const struct pll_div apll_l_init_cfg = PLL_DIVISORS(APLL_L_HZ, 12, 2);
|
||||
static const struct pll_div apll_b_init_cfg = PLL_DIVISORS(APLL_B_HZ, 1, 2);
|
||||
#if !defined(CONFIG_TPL_BUILD)
|
||||
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 1, 2);
|
||||
static const struct pll_div cpll_init_cfg = PLL_DIVISORS(CPLL_HZ, 1, 6);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static ulong rk3368_clk_get_rate(struct clk *clk);
|
||||
|
||||
/* Get pll rate by id */
|
||||
static uint32_t rkclk_pll_get_rate(struct rk3368_cru *cru,
|
||||
enum rk3368_pll_id pll_id)
|
||||
{
|
||||
uint32_t nr, no, nf;
|
||||
uint32_t con;
|
||||
struct rk3368_pll *pll = &cru->pll[pll_id];
|
||||
|
||||
con = readl(&pll->con3);
|
||||
|
||||
switch ((con & PLL_MODE_MASK) >> PLL_MODE_SHIFT) {
|
||||
case PLL_MODE_SLOW:
|
||||
return OSC_HZ;
|
||||
case PLL_MODE_NORMAL:
|
||||
con = readl(&pll->con0);
|
||||
no = ((con & PLL_OD_MASK) >> PLL_OD_SHIFT) + 1;
|
||||
nr = ((con & PLL_NR_MASK) >> PLL_NR_SHIFT) + 1;
|
||||
con = readl(&pll->con1);
|
||||
nf = ((con & PLL_NF_MASK) >> PLL_NF_SHIFT) + 1;
|
||||
|
||||
return (24 * nf / (nr * no)) * 1000000;
|
||||
case PLL_MODE_DEEP_SLOW:
|
||||
default:
|
||||
return 32768;
|
||||
}
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SPL_BUILD) || IS_ENABLED(CONFIG_TPL_BUILD)
|
||||
static int rkclk_set_pll(struct rk3368_cru *cru, enum rk3368_pll_id pll_id,
|
||||
const struct pll_div *div)
|
||||
{
|
||||
struct rk3368_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 %p: nf=%d, nr=%d, no=%d, vco=%u Hz, output=%u Hz\n",
|
||||
pll, div->nf, div->nr, div->no, vco_hz, output_hz);
|
||||
|
||||
/* enter slow mode and reset pll */
|
||||
rk_clrsetreg(&pll->con3, PLL_MODE_MASK | PLL_RESET_MASK,
|
||||
PLL_RESET << PLL_RESET_SHIFT);
|
||||
|
||||
rk_clrsetreg(&pll->con0, PLL_NR_MASK | PLL_OD_MASK,
|
||||
((div->nr - 1) << PLL_NR_SHIFT) |
|
||||
((div->no - 1) << PLL_OD_SHIFT));
|
||||
writel((div->nf - 1) << PLL_NF_SHIFT, &pll->con1);
|
||||
/*
|
||||
* BWADJ should be set to NF / 2 to ensure the nominal bandwidth.
|
||||
* Compare the RK3368 TRM, section "3.6.4 PLL Bandwidth Adjustment".
|
||||
*/
|
||||
clrsetbits_le32(&pll->con2, PLL_BWADJ_MASK, (div->nf >> 1) - 1);
|
||||
|
||||
udelay(10);
|
||||
|
||||
/* return from reset */
|
||||
rk_clrreg(&pll->con3, PLL_RESET_MASK);
|
||||
|
||||
/* waiting for pll lock */
|
||||
while (!(readl(&pll->con1) & PLL_LOCK_STA))
|
||||
udelay(1);
|
||||
|
||||
rk_clrsetreg(&pll->con3, PLL_MODE_MASK,
|
||||
PLL_MODE_NORMAL << PLL_MODE_SHIFT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SPL_BUILD) || IS_ENABLED(CONFIG_TPL_BUILD)
|
||||
static void rkclk_init(struct rk3368_cru *cru)
|
||||
{
|
||||
u32 apllb, aplll, dpll, cpll, gpll;
|
||||
|
||||
rkclk_set_pll(cru, APLLB, &apll_b_init_cfg);
|
||||
rkclk_set_pll(cru, APLLL, &apll_l_init_cfg);
|
||||
#if !defined(CONFIG_TPL_BUILD)
|
||||
/*
|
||||
* If we plan to return to the boot ROM, we can't increase the
|
||||
* GPLL rate from the SPL stage.
|
||||
*/
|
||||
rkclk_set_pll(cru, GPLL, &gpll_init_cfg);
|
||||
rkclk_set_pll(cru, CPLL, &cpll_init_cfg);
|
||||
#endif
|
||||
|
||||
apllb = rkclk_pll_get_rate(cru, APLLB);
|
||||
aplll = rkclk_pll_get_rate(cru, APLLL);
|
||||
dpll = rkclk_pll_get_rate(cru, DPLL);
|
||||
cpll = rkclk_pll_get_rate(cru, CPLL);
|
||||
gpll = rkclk_pll_get_rate(cru, GPLL);
|
||||
|
||||
debug("%s apllb(%d) apll(%d) dpll(%d) cpll(%d) gpll(%d)\n",
|
||||
__func__, apllb, aplll, dpll, cpll, gpll);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !IS_ENABLED(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(MMC_SUPPORT)
|
||||
static ulong rk3368_mmc_get_clk(struct rk3368_cru *cru, uint clk_id)
|
||||
{
|
||||
u32 div, con, con_id, rate;
|
||||
u32 pll_rate;
|
||||
|
||||
switch (clk_id) {
|
||||
case HCLK_SDMMC:
|
||||
con_id = 50;
|
||||
break;
|
||||
case HCLK_EMMC:
|
||||
con_id = 51;
|
||||
break;
|
||||
case SCLK_SDIO0:
|
||||
con_id = 48;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
con = readl(&cru->clksel_con[con_id]);
|
||||
switch (con & MMC_PLL_SEL_MASK) {
|
||||
case MMC_PLL_SEL_GPLL:
|
||||
pll_rate = rkclk_pll_get_rate(cru, GPLL);
|
||||
break;
|
||||
case MMC_PLL_SEL_24M:
|
||||
pll_rate = OSC_HZ;
|
||||
break;
|
||||
case MMC_PLL_SEL_CPLL:
|
||||
pll_rate = rkclk_pll_get_rate(cru, CPLL);
|
||||
break;
|
||||
case MMC_PLL_SEL_USBPHY_480M:
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
div = (con & MMC_CLK_DIV_MASK) >> MMC_CLK_DIV_SHIFT;
|
||||
rate = DIV_TO_RATE(pll_rate, div);
|
||||
|
||||
debug("%s: raw rate %d (post-divide by 2)\n", __func__, rate);
|
||||
return rate >> 1;
|
||||
}
|
||||
|
||||
static ulong rk3368_mmc_find_best_rate_and_parent(struct clk *clk,
|
||||
ulong rate,
|
||||
u32 *best_mux,
|
||||
u32 *best_div)
|
||||
{
|
||||
int i;
|
||||
ulong best_rate = 0;
|
||||
const ulong MHz = 1000000;
|
||||
const struct {
|
||||
u32 mux;
|
||||
ulong rate;
|
||||
} parents[] = {
|
||||
{ .mux = MMC_PLL_SEL_CPLL, .rate = CPLL_HZ },
|
||||
{ .mux = MMC_PLL_SEL_GPLL, .rate = GPLL_HZ },
|
||||
{ .mux = MMC_PLL_SEL_24M, .rate = 24 * MHz }
|
||||
};
|
||||
|
||||
debug("%s: target rate %ld\n", __func__, rate);
|
||||
for (i = 0; i < ARRAY_SIZE(parents); ++i) {
|
||||
/*
|
||||
* Find the largest rate no larger than the target-rate for
|
||||
* the current parent.
|
||||
*/
|
||||
ulong parent_rate = parents[i].rate;
|
||||
u32 div = DIV_ROUND_UP(parent_rate, rate);
|
||||
u32 adj_div = div;
|
||||
ulong new_rate = parent_rate / adj_div;
|
||||
|
||||
debug("%s: rate %ld, parent-mux %d, parent-rate %ld, div %d\n",
|
||||
__func__, rate, parents[i].mux, parents[i].rate, div);
|
||||
|
||||
/* Skip, if not representable */
|
||||
if ((div - 1) > MMC_CLK_DIV_MASK)
|
||||
continue;
|
||||
|
||||
/* Skip, if we already have a better (or equal) solution */
|
||||
if (new_rate <= best_rate)
|
||||
continue;
|
||||
|
||||
/* This is our new best rate. */
|
||||
best_rate = new_rate;
|
||||
*best_mux = parents[i].mux;
|
||||
*best_div = div - 1;
|
||||
}
|
||||
|
||||
debug("%s: best_mux = %x, best_div = %d, best_rate = %ld\n",
|
||||
__func__, *best_mux, *best_div, best_rate);
|
||||
|
||||
return best_rate;
|
||||
}
|
||||
|
||||
static ulong rk3368_mmc_set_clk(struct clk *clk, ulong rate)
|
||||
{
|
||||
struct rk3368_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
struct rk3368_cru *cru = priv->cru;
|
||||
ulong clk_id = clk->id;
|
||||
u32 con_id, mux = 0, div = 0;
|
||||
|
||||
/* Find the best parent and rate */
|
||||
rk3368_mmc_find_best_rate_and_parent(clk, rate << 1, &mux, &div);
|
||||
|
||||
switch (clk_id) {
|
||||
case HCLK_SDMMC:
|
||||
con_id = 50;
|
||||
break;
|
||||
case HCLK_EMMC:
|
||||
con_id = 51;
|
||||
break;
|
||||
case SCLK_SDIO0:
|
||||
con_id = 48;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[con_id],
|
||||
MMC_PLL_SEL_MASK | MMC_CLK_DIV_MASK,
|
||||
mux | div);
|
||||
|
||||
return rk3368_mmc_get_clk(cru, clk_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_TPL_BUILD)
|
||||
static ulong rk3368_ddr_set_clk(struct rk3368_cru *cru, ulong set_rate)
|
||||
{
|
||||
const struct pll_div *dpll_cfg = NULL;
|
||||
const ulong MHz = 1000000;
|
||||
|
||||
/* Fout = ((Fin /NR) * NF )/ NO */
|
||||
static const struct pll_div dpll_1200 = PLL_DIVISORS(1200 * MHz, 1, 1);
|
||||
static const struct pll_div dpll_1332 = PLL_DIVISORS(1332 * MHz, 2, 1);
|
||||
static const struct pll_div dpll_1600 = PLL_DIVISORS(1600 * MHz, 3, 2);
|
||||
|
||||
switch (set_rate) {
|
||||
case 1200*MHz:
|
||||
dpll_cfg = &dpll_1200;
|
||||
break;
|
||||
case 1332*MHz:
|
||||
dpll_cfg = &dpll_1332;
|
||||
break;
|
||||
case 1600*MHz:
|
||||
dpll_cfg = &dpll_1600;
|
||||
break;
|
||||
default:
|
||||
pr_err("Unsupported SDRAM frequency!,%ld\n", set_rate);
|
||||
}
|
||||
rkclk_set_pll(cru, DPLL, dpll_cfg);
|
||||
|
||||
return set_rate;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_IS_ENABLED(GMAC_ROCKCHIP)
|
||||
static ulong rk3368_gmac_set_clk(struct rk3368_cru *cru, ulong set_rate)
|
||||
{
|
||||
ulong ret;
|
||||
|
||||
/*
|
||||
* The gmac clock can be derived either from an external clock
|
||||
* or can be generated from internally by a divider from SCLK_MAC.
|
||||
*/
|
||||
if (readl(&cru->clksel_con[43]) & GMAC_MUX_SEL_EXTCLK) {
|
||||
/* An external clock will always generate the right rate... */
|
||||
ret = set_rate;
|
||||
} else {
|
||||
u32 con = readl(&cru->clksel_con[43]);
|
||||
ulong pll_rate;
|
||||
u8 div;
|
||||
|
||||
if (((con >> GMAC_PLL_SHIFT) & GMAC_PLL_MASK) ==
|
||||
GMAC_PLL_SELECT_GENERAL)
|
||||
pll_rate = GPLL_HZ;
|
||||
else if (((con >> GMAC_PLL_SHIFT) & GMAC_PLL_MASK) ==
|
||||
GMAC_PLL_SELECT_CODEC)
|
||||
pll_rate = CPLL_HZ;
|
||||
else
|
||||
/* CPLL is not set */
|
||||
return -EPERM;
|
||||
|
||||
div = DIV_ROUND_UP(pll_rate, set_rate) - 1;
|
||||
if (div <= 0x1f)
|
||||
rk_clrsetreg(&cru->clksel_con[43], GMAC_DIV_CON_MASK,
|
||||
div << GMAC_DIV_CON_SHIFT);
|
||||
else
|
||||
debug("Unsupported div for gmac:%d\n", div);
|
||||
|
||||
return DIV_TO_RATE(pll_rate, div);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* RK3368 SPI clocks have a common divider-width (7 bits) and a single bit
|
||||
* to select either CPLL or GPLL as the clock-parent. The location within
|
||||
* the enclosing CLKSEL_CON (i.e. div_shift and sel_shift) are variable.
|
||||
*/
|
||||
|
||||
struct spi_clkreg {
|
||||
uint8_t reg; /* CLKSEL_CON[reg] register in CRU */
|
||||
uint8_t div_shift;
|
||||
uint8_t sel_shift;
|
||||
};
|
||||
|
||||
/*
|
||||
* The entries are numbered relative to their offset from SCLK_SPI0.
|
||||
*/
|
||||
static const struct spi_clkreg spi_clkregs[] = {
|
||||
[0] = { .reg = 45, .div_shift = 0, .sel_shift = 7, },
|
||||
[1] = { .reg = 45, .div_shift = 8, .sel_shift = 15, },
|
||||
[2] = { .reg = 46, .div_shift = 8, .sel_shift = 15, },
|
||||
};
|
||||
|
||||
static inline u32 extract_bits(u32 val, unsigned width, unsigned shift)
|
||||
{
|
||||
return (val >> shift) & ((1 << width) - 1);
|
||||
}
|
||||
|
||||
static ulong rk3368_spi_get_clk(struct rk3368_cru *cru, ulong clk_id)
|
||||
{
|
||||
const struct spi_clkreg *spiclk = NULL;
|
||||
u32 div, val;
|
||||
|
||||
switch (clk_id) {
|
||||
case SCLK_SPI0 ... SCLK_SPI2:
|
||||
spiclk = &spi_clkregs[clk_id - SCLK_SPI0];
|
||||
break;
|
||||
|
||||
default:
|
||||
pr_err("%s: SPI clk-id %ld not supported\n", __func__, clk_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
val = readl(&cru->clksel_con[spiclk->reg]);
|
||||
div = extract_bits(val, 7, spiclk->div_shift);
|
||||
|
||||
debug("%s: div 0x%x\n", __func__, div);
|
||||
return DIV_TO_RATE(GPLL_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rk3368_spi_set_clk(struct rk3368_cru *cru, ulong clk_id, uint hz)
|
||||
{
|
||||
const struct spi_clkreg *spiclk = NULL;
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz);
|
||||
assert(src_clk_div < 127);
|
||||
|
||||
switch (clk_id) {
|
||||
case SCLK_SPI0 ... SCLK_SPI2:
|
||||
spiclk = &spi_clkregs[clk_id - SCLK_SPI0];
|
||||
break;
|
||||
|
||||
default:
|
||||
pr_err("%s: SPI clk-id %ld not supported\n", __func__, clk_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[spiclk->reg],
|
||||
((0x7f << spiclk->div_shift) |
|
||||
(0x1 << spiclk->sel_shift)),
|
||||
((src_clk_div << spiclk->div_shift) |
|
||||
(1 << spiclk->sel_shift)));
|
||||
|
||||
return rk3368_spi_get_clk(cru, clk_id);
|
||||
}
|
||||
|
||||
static ulong rk3368_saradc_get_clk(struct rk3368_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
|
||||
val = readl(&cru->clksel_con[25]);
|
||||
div = bitfield_extract(val, CLK_SARADC_DIV_CON_SHIFT,
|
||||
CLK_SARADC_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(OSC_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rk3368_saradc_set_clk(struct rk3368_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(OSC_HZ, hz) - 1;
|
||||
assert(src_clk_div < 128);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[25],
|
||||
CLK_SARADC_DIV_CON_MASK,
|
||||
src_clk_div << CLK_SARADC_DIV_CON_SHIFT);
|
||||
|
||||
return rk3368_saradc_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rk3368_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
struct rk3368_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
ulong rate = 0;
|
||||
|
||||
debug("%s: id %ld\n", __func__, clk->id);
|
||||
switch (clk->id) {
|
||||
case PLL_CPLL:
|
||||
rate = rkclk_pll_get_rate(priv->cru, CPLL);
|
||||
break;
|
||||
case PLL_GPLL:
|
||||
rate = rkclk_pll_get_rate(priv->cru, GPLL);
|
||||
break;
|
||||
case SCLK_SPI0 ... SCLK_SPI2:
|
||||
rate = rk3368_spi_get_clk(priv->cru, clk->id);
|
||||
break;
|
||||
#if !IS_ENABLED(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(MMC_SUPPORT)
|
||||
case HCLK_SDMMC:
|
||||
case HCLK_EMMC:
|
||||
rate = rk3368_mmc_get_clk(priv->cru, clk->id);
|
||||
break;
|
||||
#endif
|
||||
case SCLK_SARADC:
|
||||
rate = rk3368_saradc_get_clk(priv->cru);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
static ulong rk3368_clk_set_rate(struct clk *clk, ulong rate)
|
||||
{
|
||||
__maybe_unused struct rk3368_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
ulong ret = 0;
|
||||
|
||||
debug("%s id:%ld rate:%ld\n", __func__, clk->id, rate);
|
||||
switch (clk->id) {
|
||||
case SCLK_SPI0 ... SCLK_SPI2:
|
||||
ret = rk3368_spi_set_clk(priv->cru, clk->id, rate);
|
||||
break;
|
||||
#if IS_ENABLED(CONFIG_TPL_BUILD)
|
||||
case CLK_DDR:
|
||||
ret = rk3368_ddr_set_clk(priv->cru, rate);
|
||||
break;
|
||||
#endif
|
||||
#if !IS_ENABLED(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(MMC_SUPPORT)
|
||||
case HCLK_SDMMC:
|
||||
case HCLK_EMMC:
|
||||
ret = rk3368_mmc_set_clk(clk, rate);
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_IS_ENABLED(GMAC_ROCKCHIP)
|
||||
case SCLK_MAC:
|
||||
/* select the external clock */
|
||||
ret = rk3368_gmac_set_clk(priv->cru, rate);
|
||||
break;
|
||||
#endif
|
||||
case SCLK_SARADC:
|
||||
ret = rk3368_saradc_set_clk(priv->cru, rate);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused rk3368_gmac_set_parent(struct clk *clk, struct clk *parent)
|
||||
{
|
||||
struct rk3368_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
struct rk3368_cru *cru = priv->cru;
|
||||
const char *clock_output_name;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* If the requested parent is in the same clock-controller and
|
||||
* the id is SCLK_MAC ("sclk_mac"), switch to the internal
|
||||
* clock.
|
||||
*/
|
||||
if ((parent->dev == clk->dev) && (parent->id == SCLK_MAC)) {
|
||||
debug("%s: switching GAMC to SCLK_MAC\n", __func__);
|
||||
rk_clrreg(&cru->clksel_con[43], GMAC_MUX_SEL_EXTCLK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, we need to check the clock-output-names of the
|
||||
* requested parent to see if the requested id is "ext_gmac".
|
||||
*/
|
||||
ret = dev_read_string_index(parent->dev, "clock-output-names",
|
||||
parent->id, &clock_output_name);
|
||||
if (ret < 0)
|
||||
return -ENODATA;
|
||||
|
||||
/* If this is "ext_gmac", switch to the external clock input */
|
||||
if (!strcmp(clock_output_name, "ext_gmac")) {
|
||||
debug("%s: switching GMAC to external clock\n", __func__);
|
||||
rk_setreg(&cru->clksel_con[43], GMAC_MUX_SEL_EXTCLK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int __maybe_unused rk3368_clk_set_parent(struct clk *clk, struct clk *parent)
|
||||
{
|
||||
switch (clk->id) {
|
||||
case SCLK_MAC:
|
||||
return rk3368_gmac_set_parent(clk, parent);
|
||||
}
|
||||
|
||||
debug("%s: unsupported clk %ld\n", __func__, clk->id);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static struct clk_ops rk3368_clk_ops = {
|
||||
.get_rate = rk3368_clk_get_rate,
|
||||
.set_rate = rk3368_clk_set_rate,
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
.set_parent = rk3368_clk_set_parent,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int rk3368_clk_probe(struct udevice *dev)
|
||||
{
|
||||
struct rk3368_clk_priv __maybe_unused *priv = dev_get_priv(dev);
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
struct rk3368_clk_plat *plat = dev_get_platdata(dev);
|
||||
|
||||
priv->cru = map_sysmem(plat->dtd.reg[0], plat->dtd.reg[1]);
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_SPL_BUILD) || IS_ENABLED(CONFIG_TPL_BUILD)
|
||||
rkclk_init(priv->cru);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3368_clk_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
#if !CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
struct rk3368_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->cru = dev_read_addr_ptr(dev);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rk3368_clk_bind(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct udevice *sys_child;
|
||||
struct sysreset_reg *priv;
|
||||
|
||||
/* The reset driver does not have a device node, so bind it here */
|
||||
ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
|
||||
&sys_child);
|
||||
if (ret) {
|
||||
debug("Warning: No sysreset driver: ret=%d\n", ret);
|
||||
} else {
|
||||
priv = malloc(sizeof(struct sysreset_reg));
|
||||
priv->glb_srst_fst_value = offsetof(struct rk3368_cru,
|
||||
glb_srst_fst_val);
|
||||
priv->glb_srst_snd_value = offsetof(struct rk3368_cru,
|
||||
glb_srst_snd_val);
|
||||
sys_child->priv = priv;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(RESET_ROCKCHIP)
|
||||
ret = offsetof(struct rk3368_cru, softrst_con[0]);
|
||||
ret = rockchip_reset_bind(dev, ret, 15);
|
||||
if (ret)
|
||||
debug("Warning: software reset driver bind faile\n");
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct udevice_id rk3368_clk_ids[] = {
|
||||
{ .compatible = "rockchip,rk3368-cru" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_rk3368_cru) = {
|
||||
.name = "rockchip_rk3368_cru",
|
||||
.id = UCLASS_CLK,
|
||||
.of_match = rk3368_clk_ids,
|
||||
.priv_auto_alloc_size = sizeof(struct rk3368_clk_priv),
|
||||
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
||||
.platdata_auto_alloc_size = sizeof(struct rk3368_clk_plat),
|
||||
#endif
|
||||
.ofdata_to_platdata = rk3368_clk_ofdata_to_platdata,
|
||||
.ops = &rk3368_clk_ops,
|
||||
.bind = rk3368_clk_bind,
|
||||
.probe = rk3368_clk_probe,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,723 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2016 Rockchip Electronics Co., Ltd
|
||||
* Author: Andy Yan <andy.yan@rock-chips.com>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <bitfield.h>
|
||||
#include <clk-uclass.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch-rockchip/clock.h>
|
||||
#include <asm/arch-rockchip/cru_rv1108.h>
|
||||
#include <asm/arch-rockchip/hardware.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dt-bindings/clock/rv1108-cru.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
enum {
|
||||
VCO_MAX_HZ = 2400U * 1000000,
|
||||
VCO_MIN_HZ = 600 * 1000000,
|
||||
OUTPUT_MAX_HZ = 2400U * 1000000,
|
||||
OUTPUT_MIN_HZ = 24 * 1000000,
|
||||
};
|
||||
|
||||
#define DIV_TO_RATE(input_rate, div) ((input_rate) / ((div) + 1))
|
||||
|
||||
#define PLL_DIVISORS(hz, _refdiv, _postdiv1, _postdiv2) {\
|
||||
.refdiv = _refdiv,\
|
||||
.fbdiv = (u32)((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ),\
|
||||
.postdiv1 = _postdiv1, .postdiv2 = _postdiv2};\
|
||||
_Static_assert(((u64)hz * _refdiv * _postdiv1 * _postdiv2 / OSC_HZ) *\
|
||||
OSC_HZ / (_refdiv * _postdiv1 * _postdiv2) == hz,\
|
||||
#hz "Hz cannot be hit with PLL "\
|
||||
"divisors on line " __stringify(__LINE__));
|
||||
|
||||
static const struct pll_div apll_init_cfg = PLL_DIVISORS(APLL_HZ, 1, 3, 1);
|
||||
static const struct pll_div gpll_init_cfg = PLL_DIVISORS(GPLL_HZ, 2, 2, 1);
|
||||
|
||||
/* use integer mode */
|
||||
static inline int rv1108_pll_id(enum rk_clk_id clk_id)
|
||||
{
|
||||
int id = 0;
|
||||
|
||||
switch (clk_id) {
|
||||
case CLK_ARM:
|
||||
case CLK_DDR:
|
||||
id = clk_id - 1;
|
||||
break;
|
||||
case CLK_GENERAL:
|
||||
id = 2;
|
||||
break;
|
||||
default:
|
||||
printf("invalid pll id:%d\n", clk_id);
|
||||
id = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static int rkclk_set_pll(struct rv1108_cru *cru, enum rk_clk_id clk_id,
|
||||
const struct pll_div *div)
|
||||
{
|
||||
int pll_id = rv1108_pll_id(clk_id);
|
||||
struct rv1108_pll *pll = &cru->pll[pll_id];
|
||||
|
||||
/* All PLLs have same VCO and output frequency range restrictions. */
|
||||
uint vco_hz = OSC_HZ / 1000 * div->fbdiv / div->refdiv * 1000;
|
||||
uint output_hz = vco_hz / div->postdiv1 / div->postdiv2;
|
||||
|
||||
debug("PLL at %p: fb=%d, ref=%d, pst1=%d, pst2=%d, vco=%u Hz, output=%u Hz\n",
|
||||
pll, div->fbdiv, div->refdiv, div->postdiv1,
|
||||
div->postdiv2, vco_hz, output_hz);
|
||||
assert(vco_hz >= VCO_MIN_HZ && vco_hz <= VCO_MAX_HZ &&
|
||||
output_hz >= OUTPUT_MIN_HZ && output_hz <= OUTPUT_MAX_HZ);
|
||||
|
||||
/*
|
||||
* When power on or changing PLL setting,
|
||||
* we must force PLL into slow mode to ensure output stable clock.
|
||||
*/
|
||||
rk_clrsetreg(&pll->con3, WORK_MODE_MASK,
|
||||
WORK_MODE_SLOW << WORK_MODE_SHIFT);
|
||||
|
||||
/* use integer mode */
|
||||
rk_setreg(&pll->con3, 1 << DSMPD_SHIFT);
|
||||
/* Power down */
|
||||
rk_setreg(&pll->con3, 1 << GLOBAL_POWER_DOWN_SHIFT);
|
||||
|
||||
rk_clrsetreg(&pll->con0, FBDIV_MASK, div->fbdiv << FBDIV_SHIFT);
|
||||
rk_clrsetreg(&pll->con1, POSTDIV1_MASK | POSTDIV2_MASK | REFDIV_MASK,
|
||||
(div->postdiv1 << POSTDIV1_SHIFT |
|
||||
div->postdiv2 << POSTDIV2_SHIFT |
|
||||
div->refdiv << REFDIV_SHIFT));
|
||||
rk_clrsetreg(&pll->con2, FRACDIV_MASK,
|
||||
(div->refdiv << REFDIV_SHIFT));
|
||||
|
||||
/* Power Up */
|
||||
rk_clrreg(&pll->con3, 1 << GLOBAL_POWER_DOWN_SHIFT);
|
||||
|
||||
/* waiting for pll lock */
|
||||
while (readl(&pll->con2) & (1 << LOCK_STA_SHIFT))
|
||||
udelay(1);
|
||||
|
||||
/*
|
||||
* set PLL into normal mode.
|
||||
*/
|
||||
rk_clrsetreg(&pll->con3, WORK_MODE_MASK,
|
||||
WORK_MODE_NORMAL << WORK_MODE_SHIFT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t rkclk_pll_get_rate(struct rv1108_cru *cru,
|
||||
enum rk_clk_id clk_id)
|
||||
{
|
||||
uint32_t refdiv, fbdiv, postdiv1, postdiv2;
|
||||
uint32_t con0, con1, con3;
|
||||
int pll_id = rv1108_pll_id(clk_id);
|
||||
struct rv1108_pll *pll = &cru->pll[pll_id];
|
||||
uint32_t freq;
|
||||
|
||||
con3 = readl(&pll->con3);
|
||||
|
||||
if (con3 & WORK_MODE_MASK) {
|
||||
con0 = readl(&pll->con0);
|
||||
con1 = readl(&pll->con1);
|
||||
fbdiv = (con0 >> FBDIV_SHIFT) & FBDIV_MASK;
|
||||
postdiv1 = (con1 & POSTDIV1_MASK) >> POSTDIV1_SHIFT;
|
||||
postdiv2 = (con1 & POSTDIV2_MASK) >> POSTDIV2_SHIFT;
|
||||
refdiv = (con1 >> REFDIV_SHIFT) & REFDIV_MASK;
|
||||
freq = (24 * fbdiv / (refdiv * postdiv1 * postdiv2)) * 1000000;
|
||||
} else {
|
||||
freq = OSC_HZ;
|
||||
}
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
static int rv1108_mac_set_clk(struct rv1108_cru *cru, ulong rate)
|
||||
{
|
||||
uint32_t con = readl(&cru->clksel_con[24]);
|
||||
ulong pll_rate;
|
||||
uint8_t div;
|
||||
|
||||
if ((con >> MAC_PLL_SEL_SHIFT) & MAC_PLL_SEL_GPLL)
|
||||
pll_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
else
|
||||
pll_rate = rkclk_pll_get_rate(cru, CLK_ARM);
|
||||
|
||||
/*default set 50MHZ for gmac*/
|
||||
if (!rate)
|
||||
rate = 50000000;
|
||||
|
||||
div = DIV_ROUND_UP(pll_rate, rate) - 1;
|
||||
if (div <= 0x1f)
|
||||
rk_clrsetreg(&cru->clksel_con[24], MAC_CLK_DIV_MASK,
|
||||
div << MAC_CLK_DIV_SHIFT);
|
||||
else
|
||||
debug("Unsupported div for gmac:%d\n", div);
|
||||
|
||||
return DIV_TO_RATE(pll_rate, div);
|
||||
}
|
||||
|
||||
static int rv1108_sfc_set_clk(struct rv1108_cru *cru, uint rate)
|
||||
{
|
||||
u32 con = readl(&cru->clksel_con[27]);
|
||||
u32 pll_rate;
|
||||
u32 div;
|
||||
|
||||
if ((con >> SFC_PLL_SEL_SHIFT) && SFC_PLL_SEL_GPLL)
|
||||
pll_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
else
|
||||
pll_rate = rkclk_pll_get_rate(cru, CLK_DDR);
|
||||
|
||||
div = DIV_ROUND_UP(pll_rate, rate) - 1;
|
||||
if (div <= 0x3f)
|
||||
rk_clrsetreg(&cru->clksel_con[27], SFC_CLK_DIV_MASK,
|
||||
div << SFC_CLK_DIV_SHIFT);
|
||||
else
|
||||
debug("Unsupported sfc clk rate:%d\n", rate);
|
||||
|
||||
return DIV_TO_RATE(pll_rate, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_saradc_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
|
||||
val = readl(&cru->clksel_con[22]);
|
||||
div = bitfield_extract(val, CLK_SARADC_DIV_CON_SHIFT,
|
||||
CLK_SARADC_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(OSC_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_saradc_set_clk(struct rv1108_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(OSC_HZ, hz) - 1;
|
||||
assert(src_clk_div < 128);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[22],
|
||||
CLK_SARADC_DIV_CON_MASK,
|
||||
src_clk_div << CLK_SARADC_DIV_CON_SHIFT);
|
||||
|
||||
return rv1108_saradc_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rv1108_aclk_vio1_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
|
||||
val = readl(&cru->clksel_con[28]);
|
||||
div = bitfield_extract(val, ACLK_VIO1_CLK_DIV_SHIFT,
|
||||
CLK_VIO_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(GPLL_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_aclk_vio1_set_clk(struct rv1108_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz) - 1;
|
||||
assert(src_clk_div < 32);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[28],
|
||||
ACLK_VIO1_CLK_DIV_MASK | ACLK_VIO1_PLL_SEL_MASK,
|
||||
(src_clk_div << ACLK_VIO1_CLK_DIV_SHIFT) |
|
||||
(VIO_PLL_SEL_GPLL << ACLK_VIO1_PLL_SEL_SHIFT));
|
||||
|
||||
return rv1108_aclk_vio1_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rv1108_aclk_vio0_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
|
||||
val = readl(&cru->clksel_con[28]);
|
||||
div = bitfield_extract(val, ACLK_VIO0_CLK_DIV_SHIFT,
|
||||
CLK_VIO_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(GPLL_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_aclk_vio0_set_clk(struct rv1108_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz) - 1;
|
||||
assert(src_clk_div < 32);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[28],
|
||||
ACLK_VIO0_CLK_DIV_MASK | ACLK_VIO0_PLL_SEL_MASK,
|
||||
(src_clk_div << ACLK_VIO0_CLK_DIV_SHIFT) |
|
||||
(VIO_PLL_SEL_GPLL << ACLK_VIO0_PLL_SEL_SHIFT));
|
||||
|
||||
/*HCLK_VIO default div = 4*/
|
||||
rk_clrsetreg(&cru->clksel_con[29],
|
||||
HCLK_VIO_CLK_DIV_MASK,
|
||||
3 << HCLK_VIO_CLK_DIV_SHIFT);
|
||||
/*PCLK_VIO default div = 4*/
|
||||
rk_clrsetreg(&cru->clksel_con[29],
|
||||
PCLK_VIO_CLK_DIV_MASK,
|
||||
3 << PCLK_VIO_CLK_DIV_SHIFT);
|
||||
|
||||
return rv1108_aclk_vio0_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rv1108_dclk_vop_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
|
||||
val = readl(&cru->clksel_con[32]);
|
||||
div = bitfield_extract(val, DCLK_VOP_CLK_DIV_SHIFT,
|
||||
DCLK_VOP_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(GPLL_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_dclk_vop_set_clk(struct rv1108_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(GPLL_HZ, hz) - 1;
|
||||
assert(src_clk_div < 64);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[32],
|
||||
DCLK_VOP_CLK_DIV_MASK | DCLK_VOP_PLL_SEL_MASK |
|
||||
DCLK_VOP_SEL_SHIFT,
|
||||
(src_clk_div << DCLK_VOP_CLK_DIV_SHIFT) |
|
||||
(DCLK_VOP_PLL_SEL_GPLL << DCLK_VOP_PLL_SEL_SHIFT) |
|
||||
(DCLK_VOP_SEL_PLL << DCLK_VOP_SEL_SHIFT));
|
||||
|
||||
return rv1108_dclk_vop_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rv1108_aclk_bus_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
ulong parent_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
val = readl(&cru->clksel_con[2]);
|
||||
div = bitfield_extract(val, ACLK_BUS_DIV_CON_SHIFT,
|
||||
ACLK_BUS_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(parent_rate, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_aclk_bus_set_clk(struct rv1108_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
ulong parent_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(parent_rate, hz) - 1;
|
||||
assert(src_clk_div < 32);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[2],
|
||||
ACLK_BUS_DIV_CON_MASK | ACLK_BUS_PLL_SEL_MASK,
|
||||
(src_clk_div << ACLK_BUS_DIV_CON_SHIFT) |
|
||||
(ACLK_BUS_PLL_SEL_GPLL << ACLK_BUS_PLL_SEL_SHIFT));
|
||||
|
||||
return rv1108_aclk_bus_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rv1108_aclk_peri_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
ulong parent_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
val = readl(&cru->clksel_con[23]);
|
||||
div = bitfield_extract(val, ACLK_PERI_DIV_CON_SHIFT,
|
||||
PERI_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(parent_rate, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_hclk_peri_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
ulong parent_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
val = readl(&cru->clksel_con[23]);
|
||||
div = bitfield_extract(val, HCLK_PERI_DIV_CON_SHIFT,
|
||||
PERI_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(parent_rate, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_pclk_peri_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, val;
|
||||
ulong parent_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
val = readl(&cru->clksel_con[23]);
|
||||
div = bitfield_extract(val, PCLK_PERI_DIV_CON_SHIFT,
|
||||
PERI_DIV_CON_WIDTH);
|
||||
|
||||
return DIV_TO_RATE(parent_rate, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_aclk_peri_set_clk(struct rv1108_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
ulong parent_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(parent_rate, hz) - 1;
|
||||
assert(src_clk_div < 32);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[23],
|
||||
ACLK_PERI_DIV_CON_MASK | ACLK_PERI_PLL_SEL_MASK,
|
||||
(src_clk_div << ACLK_PERI_DIV_CON_SHIFT) |
|
||||
(ACLK_PERI_PLL_SEL_GPLL << ACLK_PERI_PLL_SEL_SHIFT));
|
||||
|
||||
return rv1108_aclk_peri_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rv1108_hclk_peri_set_clk(struct rv1108_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
ulong parent_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(parent_rate, hz) - 1;
|
||||
assert(src_clk_div < 32);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[23],
|
||||
HCLK_PERI_DIV_CON_MASK,
|
||||
(src_clk_div << HCLK_PERI_DIV_CON_SHIFT));
|
||||
|
||||
return rv1108_hclk_peri_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rv1108_pclk_peri_set_clk(struct rv1108_cru *cru, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
ulong parent_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
src_clk_div = DIV_ROUND_UP(parent_rate, hz) - 1;
|
||||
assert(src_clk_div < 32);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[23],
|
||||
PCLK_PERI_DIV_CON_MASK,
|
||||
(src_clk_div << PCLK_PERI_DIV_CON_SHIFT));
|
||||
|
||||
return rv1108_pclk_peri_get_clk(cru);
|
||||
}
|
||||
|
||||
static ulong rv1108_i2c_get_clk(struct rv1108_cru *cru, ulong clk_id)
|
||||
{
|
||||
u32 div, con;
|
||||
|
||||
switch (clk_id) {
|
||||
case SCLK_I2C0_PMU:
|
||||
con = readl(&cru->clksel_con[19]);
|
||||
div = bitfield_extract(con, CLK_I2C0_DIV_CON_SHIFT,
|
||||
I2C_DIV_CON_WIDTH);
|
||||
break;
|
||||
case SCLK_I2C1:
|
||||
con = readl(&cru->clksel_con[19]);
|
||||
div = bitfield_extract(con, CLK_I2C1_DIV_CON_SHIFT,
|
||||
I2C_DIV_CON_WIDTH);
|
||||
break;
|
||||
case SCLK_I2C2:
|
||||
con = readl(&cru->clksel_con[20]);
|
||||
div = bitfield_extract(con, CLK_I2C2_DIV_CON_SHIFT,
|
||||
I2C_DIV_CON_WIDTH);
|
||||
break;
|
||||
case SCLK_I2C3:
|
||||
con = readl(&cru->clksel_con[20]);
|
||||
div = bitfield_extract(con, CLK_I2C3_DIV_CON_SHIFT,
|
||||
I2C_DIV_CON_WIDTH);
|
||||
break;
|
||||
default:
|
||||
printf("do not support this i2c bus\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return DIV_TO_RATE(GPLL_HZ, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_i2c_set_clk(struct rv1108_cru *cru, ulong clk_id, uint hz)
|
||||
{
|
||||
int src_clk_div;
|
||||
|
||||
/* i2c0,4,8 src clock from ppll, i2c1,2,3,5,6,7 src clock from gpll*/
|
||||
src_clk_div = GPLL_HZ / hz;
|
||||
assert(src_clk_div - 1 <= 127);
|
||||
|
||||
switch (clk_id) {
|
||||
case SCLK_I2C0_PMU:
|
||||
rk_clrsetreg(&cru->clksel_con[19],
|
||||
CLK_I2C0_DIV_CON_MASK | CLK_I2C1_PLL_SEL_MASK,
|
||||
(src_clk_div << CLK_I2C0_DIV_CON_SHIFT) |
|
||||
(CLK_I2C1_PLL_SEL_GPLL << CLK_I2C1_PLL_SEL_SHIFT));
|
||||
break;
|
||||
case SCLK_I2C1:
|
||||
rk_clrsetreg(&cru->clksel_con[19],
|
||||
CLK_I2C1_DIV_CON_MASK | CLK_I2C1_PLL_SEL_MASK,
|
||||
(src_clk_div << CLK_I2C1_DIV_CON_SHIFT) |
|
||||
(CLK_I2C1_PLL_SEL_GPLL << CLK_I2C1_PLL_SEL_SHIFT));
|
||||
break;
|
||||
case SCLK_I2C2:
|
||||
rk_clrsetreg(&cru->clksel_con[20],
|
||||
CLK_I2C2_DIV_CON_MASK | CLK_I2C3_PLL_SEL_MASK,
|
||||
(src_clk_div << CLK_I2C2_DIV_CON_SHIFT) |
|
||||
(CLK_I2C3_PLL_SEL_GPLL << CLK_I2C3_PLL_SEL_SHIFT));
|
||||
break;
|
||||
case SCLK_I2C3:
|
||||
rk_clrsetreg(&cru->clksel_con[20],
|
||||
CLK_I2C3_DIV_CON_MASK | CLK_I2C3_PLL_SEL_MASK,
|
||||
(src_clk_div << CLK_I2C3_DIV_CON_SHIFT) |
|
||||
(CLK_I2C3_PLL_SEL_GPLL << CLK_I2C3_PLL_SEL_SHIFT));
|
||||
break;
|
||||
default:
|
||||
printf("do not support this i2c bus\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return rv1108_i2c_get_clk(cru, clk_id);
|
||||
}
|
||||
|
||||
static ulong rv1108_mmc_get_clk(struct rv1108_cru *cru)
|
||||
{
|
||||
u32 div, con;
|
||||
ulong mmc_clk;
|
||||
|
||||
con = readl(&cru->clksel_con[26]);
|
||||
div = bitfield_extract(con, EMMC_CLK_DIV_SHIFT, 8);
|
||||
|
||||
con = readl(&cru->clksel_con[25]);
|
||||
|
||||
if ((con & EMMC_PLL_SEL_MASK) >> EMMC_PLL_SEL_SHIFT == EMMC_PLL_SEL_OSC)
|
||||
mmc_clk = DIV_TO_RATE(OSC_HZ, div) / 2;
|
||||
else
|
||||
mmc_clk = DIV_TO_RATE(GPLL_HZ, div) / 2;
|
||||
|
||||
debug("%s div %d get_clk %ld\n", __func__, div, mmc_clk);
|
||||
return mmc_clk;
|
||||
}
|
||||
|
||||
static ulong rv1108_mmc_set_clk(struct rv1108_cru *cru, ulong rate)
|
||||
{
|
||||
int div;
|
||||
u32 pll_rate;
|
||||
|
||||
div = DIV_ROUND_UP(rkclk_pll_get_rate(cru, CLK_GENERAL), rate);
|
||||
|
||||
if (div < 127) {
|
||||
debug("%s source gpll\n", __func__);
|
||||
rk_clrsetreg(&cru->clksel_con[25], EMMC_PLL_SEL_MASK,
|
||||
(EMMC_PLL_SEL_GPLL << EMMC_PLL_SEL_SHIFT));
|
||||
pll_rate = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
} else {
|
||||
debug("%s source 24m\n", __func__);
|
||||
rk_clrsetreg(&cru->clksel_con[25], EMMC_PLL_SEL_MASK,
|
||||
(EMMC_PLL_SEL_OSC << EMMC_PLL_SEL_SHIFT));
|
||||
pll_rate = OSC_HZ;
|
||||
}
|
||||
|
||||
div = DIV_ROUND_UP(pll_rate / 2, rate);
|
||||
rk_clrsetreg(&cru->clksel_con[26], EMMC_CLK_DIV_MASK,
|
||||
((div - 1) << EMMC_CLK_DIV_SHIFT));
|
||||
|
||||
debug("%s set_rate %ld div %d\n", __func__, rate, div);
|
||||
|
||||
return DIV_TO_RATE(pll_rate, div);
|
||||
}
|
||||
|
||||
static ulong rv1108_clk_get_rate(struct clk *clk)
|
||||
{
|
||||
struct rv1108_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
|
||||
switch (clk->id) {
|
||||
case 0 ... 63:
|
||||
return rkclk_pll_get_rate(priv->cru, clk->id);
|
||||
case SCLK_SARADC:
|
||||
return rv1108_saradc_get_clk(priv->cru);
|
||||
case ACLK_VIO0:
|
||||
return rv1108_aclk_vio0_get_clk(priv->cru);
|
||||
case ACLK_VIO1:
|
||||
return rv1108_aclk_vio1_get_clk(priv->cru);
|
||||
case DCLK_VOP:
|
||||
return rv1108_dclk_vop_get_clk(priv->cru);
|
||||
case ACLK_PRE:
|
||||
return rv1108_aclk_bus_get_clk(priv->cru);
|
||||
case ACLK_PERI:
|
||||
return rv1108_aclk_peri_get_clk(priv->cru);
|
||||
case HCLK_PERI:
|
||||
return rv1108_hclk_peri_get_clk(priv->cru);
|
||||
case PCLK_PERI:
|
||||
return rv1108_pclk_peri_get_clk(priv->cru);
|
||||
case SCLK_I2C0_PMU:
|
||||
case SCLK_I2C1:
|
||||
case SCLK_I2C2:
|
||||
case SCLK_I2C3:
|
||||
return rv1108_i2c_get_clk(priv->cru, clk->id);
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
case SCLK_EMMC_SAMPLE:
|
||||
return rv1108_mmc_get_clk(priv->cru);
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
static ulong rv1108_clk_set_rate(struct clk *clk, ulong rate)
|
||||
{
|
||||
struct rv1108_clk_priv *priv = dev_get_priv(clk->dev);
|
||||
ulong new_rate;
|
||||
|
||||
switch (clk->id) {
|
||||
case SCLK_MAC:
|
||||
new_rate = rv1108_mac_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case SCLK_SFC:
|
||||
new_rate = rv1108_sfc_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case SCLK_SARADC:
|
||||
new_rate = rv1108_saradc_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case ACLK_VIO0:
|
||||
new_rate = rv1108_aclk_vio0_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case ACLK_VIO1:
|
||||
new_rate = rv1108_aclk_vio1_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case DCLK_VOP:
|
||||
new_rate = rv1108_dclk_vop_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case ACLK_PRE:
|
||||
new_rate = rv1108_aclk_bus_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case ACLK_PERI:
|
||||
new_rate = rv1108_aclk_peri_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case HCLK_PERI:
|
||||
new_rate = rv1108_hclk_peri_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case PCLK_PERI:
|
||||
new_rate = rv1108_pclk_peri_set_clk(priv->cru, rate);
|
||||
break;
|
||||
case SCLK_I2C0_PMU:
|
||||
case SCLK_I2C1:
|
||||
case SCLK_I2C2:
|
||||
case SCLK_I2C3:
|
||||
new_rate = rv1108_i2c_set_clk(priv->cru, clk->id, rate);
|
||||
break;
|
||||
case HCLK_EMMC:
|
||||
case SCLK_EMMC:
|
||||
new_rate = rv1108_mmc_set_clk(priv->cru, rate);
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return new_rate;
|
||||
}
|
||||
|
||||
static const struct clk_ops rv1108_clk_ops = {
|
||||
.get_rate = rv1108_clk_get_rate,
|
||||
.set_rate = rv1108_clk_set_rate,
|
||||
};
|
||||
|
||||
static void rkclk_init(struct rv1108_cru *cru)
|
||||
{
|
||||
unsigned int apll, dpll, gpll;
|
||||
unsigned int aclk_bus, aclk_peri, hclk_peri, pclk_peri;
|
||||
|
||||
aclk_bus = rv1108_aclk_bus_set_clk(cru, ACLK_BUS_HZ / 2);
|
||||
aclk_peri = rv1108_aclk_peri_set_clk(cru, ACLK_PERI_HZ / 2);
|
||||
hclk_peri = rv1108_hclk_peri_set_clk(cru, HCLK_PERI_HZ / 2);
|
||||
pclk_peri = rv1108_pclk_peri_set_clk(cru, PCLK_PERI_HZ / 2);
|
||||
rv1108_aclk_vio0_set_clk(cru, 297000000);
|
||||
rv1108_aclk_vio1_set_clk(cru, 297000000);
|
||||
|
||||
/* configure apll */
|
||||
rkclk_set_pll(cru, CLK_ARM, &apll_init_cfg);
|
||||
rkclk_set_pll(cru, CLK_GENERAL, &gpll_init_cfg);
|
||||
aclk_bus = rv1108_aclk_bus_set_clk(cru, ACLK_BUS_HZ);
|
||||
aclk_peri = rv1108_aclk_peri_set_clk(cru, ACLK_PERI_HZ);
|
||||
hclk_peri = rv1108_hclk_peri_set_clk(cru, HCLK_PERI_HZ);
|
||||
pclk_peri = rv1108_pclk_peri_set_clk(cru, PCLK_PERI_HZ);
|
||||
|
||||
apll = rkclk_pll_get_rate(cru, CLK_ARM);
|
||||
dpll = rkclk_pll_get_rate(cru, CLK_DDR);
|
||||
gpll = rkclk_pll_get_rate(cru, CLK_GENERAL);
|
||||
|
||||
rk_clrsetreg(&cru->clksel_con[0], CORE_CLK_DIV_MASK,
|
||||
0 << MAC_CLK_DIV_SHIFT);
|
||||
|
||||
printf("APLL: %d DPLL:%d GPLL:%d\n", apll, dpll, gpll);
|
||||
printf("ACLK_BUS: %d ACLK_PERI:%d HCLK_PERI:%d PCLK_PERI:%d\n",
|
||||
aclk_bus, aclk_peri, hclk_peri, pclk_peri);
|
||||
}
|
||||
|
||||
static int rv1108_clk_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct rv1108_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->cru = dev_read_addr_ptr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rv1108_clk_probe(struct udevice *dev)
|
||||
{
|
||||
struct rv1108_clk_priv *priv = dev_get_priv(dev);
|
||||
|
||||
rkclk_init(priv->cru);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rv1108_clk_bind(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
struct udevice *sys_child;
|
||||
struct sysreset_reg *priv;
|
||||
|
||||
/* The reset driver does not have a device node, so bind it here */
|
||||
ret = device_bind_driver(dev, "rockchip_sysreset", "sysreset",
|
||||
&sys_child);
|
||||
if (ret) {
|
||||
debug("Warning: No sysreset driver: ret=%d\n", ret);
|
||||
} else {
|
||||
priv = malloc(sizeof(struct sysreset_reg));
|
||||
priv->glb_srst_fst_value = offsetof(struct rv1108_cru,
|
||||
glb_srst_fst_val);
|
||||
priv->glb_srst_snd_value = offsetof(struct rv1108_cru,
|
||||
glb_srst_snd_val);
|
||||
sys_child->priv = priv;
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(RESET_ROCKCHIP)
|
||||
ret = offsetof(struct rv1108_cru, softrst_con[0]);
|
||||
ret = rockchip_reset_bind(dev, ret, 13);
|
||||
if (ret)
|
||||
debug("Warning: software reset driver bind faile\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id rv1108_clk_ids[] = {
|
||||
{ .compatible = "rockchip,rv1108-cru" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(clk_rv1108) = {
|
||||
.name = "clk_rv1108",
|
||||
.id = UCLASS_CLK,
|
||||
.of_match = rv1108_clk_ids,
|
||||
.priv_auto_alloc_size = sizeof(struct rv1108_clk_priv),
|
||||
.ops = &rv1108_clk_ops,
|
||||
.bind = rv1108_clk_bind,
|
||||
.ofdata_to_platdata = rv1108_clk_ofdata_to_platdata,
|
||||
.probe = rv1108_clk_probe,
|
||||
};
|
||||
Reference in New Issue
Block a user