1. 时钟系统结构
rockchip的时钟系统代码位于drivers/clk/rockchip
,目录整体结构如下:
├── rockchip
│ ├── clk.c---------------时钟系统注册
│ ├── clk-cpu.c-----------CPU调频
│ ├── clk-ddr.c-----------DDR调频
│ ├── clk-half-divider.c--二分频
│ ├── clk-inverter.c------极性翻转
│ ├── clk-mmc-phase.c-----mmc相位
│ ├── clk-muxgrf.c--------时钟复用
│ ├── clk-pll.c-----------PLL配置
│ ├── clk-pvtm.c----------电压温度检测调频
│ ├── clk-rk3399.c--------RK3399时钟核心初始化、PLL初始化、使能控制、分频等
│ ├── clk-rockchip.c------RK系列芯片时钟
└──────────────────────────────────────────────────────
从上面的文件结构中我们可以看到,rockchip的时钟系统基本包含了CCF框架提到的时钟使能、复用、分频、倍频等功能。RK3399的时钟系统主体是clk-rk3399.c
这个文件。
2. 初始化方式
kernel中的时钟初始化代码位于驱动初始化这个层面,相较于CPU初始化而言,它是比较靠后的。在此之前,CPU及基本外设的工作时钟,由bootrom或者U-Boot及其他bootloader配置,有的CPU在时钟系统完全工作之前是工作于低频模式,而有的CPU则上电解复位之后便工作于高频模式,不可一概而论。
说到kernel中的时钟系统初始化,就不得不提CLK_OF_DECLARE
这个宏定义,在时钟驱动中通过它来声明初始化函数,其实现原理如下图所示:
3. RK3399时钟系统实现
rockchip为全系列芯片提供了统一的时钟初始化API,这一点非常值得我们借鉴同时也是很好的一种代码架构模式。RK3399的时钟初始化涉及两部分内容,分别是cru
和pmu
,二者原理相通,本文以cru
初始化为例。具体包括:
上图中涉及到的CCF文件在前文已经描述过:
3.1 核心初始化
RK3399基于数据结构rockchip_clk_provider
描述整体的时钟结构,以此代表clock provider
。
struct rockchip_clk_provider {
void __iomem *reg_base;
struct clk_onecell_data clk_data;
struct device_node *cru_node;
struct regmap *grf;
struct regmap *pmugrf;
spinlock_t lock;
};
在设备树rk3399.dtsi
文件中以grf: syscon@ff770000
和pmugrf: syscon@ff320000
定义了grf
和pmugrf
,包含了时钟控制相关的寄存器。
通过rockchip_clk_init()
例化数据结构struct rockchip_clk_provider
:
ctx->reg_base = base;
ctx->clk_data.clks = clk_table;
ctx->clk_data.clk_num = nr_clks; //---CLK数量,共497路
ctx->cru_node = np; //---设备树中的cru结点
ctx->grf = ERR_PTR(-EPROBE_DEFER); //---设备树中的grf结点
ctx->pmugrf = ERR_PTR(-EPROBE_DEFER); //---设备树中的pmugrf结点
spin_lock_init(&ctx->lock);
ctx->grf = syscon_regmap_lookup_by_phandle(ctx->cru_node,"rockchip,grf");
ctx->pmugrf = syscon_regmap_lookup_by_phandle(ctx->cru_node,"rockchip,pmugrf");
3.2 PLL初始化
RK3399芯片内部包含了8个PLL,分别是LPLL, BPLL, CPLL, GPLL, NPLL, VPLL, VPLL, PPLL
,如下图所示:
在代码中使用数据结构struct rockchip_pll_clock
描述RK3399的PLL:
struct rockchip_pll_clock {
unsigned int id;
const char *name;
const char *const *parent_names;
u8 num_parents;
unsigned long flags;
int con_offset;
int mode_offset;
int mode_shift;
int lock_shift;
enum rockchip_pll_type type;
u8 pll_flags;
struct rockchip_pll_rate_table *rate_table;
};
PLL的配置在程序中进行固化,如下:
static struct rockchip_pll_clock rk3399_pll_clks[] __initdata = {
[lpll] = PLL(pll_rk3399, PLL_APLLL, "lpll", mux_pll_p, 0, RK3399_PLL_CON(0),RK3399_PLL_CON(3), 8, 31, 0, rk3399_pll_rates),
[bpll] = PLL(pll_rk3399, PLL_APLLB, "bpll", mux_pll_p, 0, RK3399_PLL_CON(8),RK3399_PLL_CON(11), 8, 31, 0, rk3399_pll_rates),
[dpll] = PLL(pll_rk3399, PLL_DPLL, "dpll", mux_pll_p, 0, RK3399_PLL_CON(16),RK3399_PLL_CON(19), 8, 31, 0, NULL),
#ifdef RK3399_TWO_PLL_FOR_VOP
[cpll] = PLL(pll_rk3399, PLL_CPLL, "cpll", mux_pll_p, 0, RK3399_PLL_CON(24),RK3399_PLL_CON(27), 8, 31, 0, rk3399_pll_rates),
#else
[cpll] = PLL(pll_rk3399, PLL_CPLL, "cpll", mux_pll_p, 0, RK3399_PLL_CON(24),RK3399_PLL_CON(27), 8, 31, ROCKCHIP_PLL_SYNC_RATE, rk3399_pll_rates),
#endif
[gpll] = PLL(pll_rk3399, PLL_GPLL, "gpll", mux_pll_p, 0, RK3399_PLL_CON(32),RK3399_PLL_CON(35), 8, 31, 0, rk3399_pll_rates),
[npll] = PLL(pll_rk3399, PLL_NPLL, "npll", mux_pll_p, 0, RK3399_PLL_CON(40),RK3399_PLL_CON(43), 8, 31, ROCKCHIP_PLL_SYNC_RATE, rk3399_pll_rates),
[vpll] = PLL(pll_rk3399, PLL_VPLL, "vpll", mux_pll_p, 0, RK3399_PLL_CON(48),RK3399_PLL_CON(51), 8, 31, 0, rk3399_vpll_rates),
};
通过rockchip_clk_register_pll()
函数初始化PLL,主要是例化数据结构struct rockchip_clk_pll
并完成时钟的注册,重点包含如下几个方面:
pll->pll_mux_ops = &clk_mux_ops; //--PLL复用
init.ops =
&rockchip_rk3399_pll_clk_ops;//--PLL控制
pll->hw.init = &init; //--PLL数据
pll->type = pll_type; //--PLL类型
pll->ctx = ctx; //--关联时钟核心
上面的rockchip_rk3399_pll_clk_ops
数据结构包含了PLL一系列的控制函数,如:
static const struct clk_ops rockchip_rk3399_pll_clk_ops = {
.recalc_rate = rockchip_rk3399_pll_recalc_rate,
.round_rate = rockchip_pll_round_rate,
.set_rate = rockchip_rk3399_pll_set_rate,
.enable = rockchip_rk3399_pll_enable,
.disable = rockchip_rk3399_pll_disable,
.is_enabled = rockchip_rk3399_pll_is_enabled,
.init = rockchip_rk3399_pll_init,
};
3.2 复用/分频/GATE
这部分的初始化内容不限于标题所述,只是这3部分内容更为重要而已。
在代码中通过数据结构rockchip_clk_branch
描述RK3399时钟系统的控制功能,如下:
struct rockchip_clk_branch {
unsigned int id;
enum rockchip_clk_branch_type branch_type;
const char *name;
const char *const *parent_names;
u8 num_parents;
unsigned long flags;
...
};
其相关配置在程序中固化,基本涵盖了cru
时钟单元的所有输出时钟控制信息。
static struct rockchip_clk_branch rk3399_clk_branches[] __initdata = {
GATE(SCLK_USB2PHY0_REF, "clk_usb2phy0_ref", "xin24m", CLK_IGNORE_UNUSED,RK3399_CLKGATE_CON(6), 5, GFLAGS),
GATE(SCLK_USB2PHY1_REF, "clk_usb2phy1_ref", "xin24m", CLK_IGNORE_UNUSED,RK3399_CLKGATE_CON(6), 6, GFLAGS),
GATE(SCLK_USBPHY0_480M_SRC, "clk_usbphy0_480m_src", "clk_usbphy0_480m", 0,RK3399_CLKGATE_CON(13), 12, GFLAGS),
GATE(SCLK_USBPHY1_480M_SRC, "clk_usbphy1_480m_src", "clk_usbphy1_480m", 0,RK3399_CLKGATE_CON(13), 12, GFLAGS),
MUX(0, "clk_usbphy_480m", mux_usbphy_480m_p, 0,RK3399_CLKSEL_CON(14), 6, 1, MFLAGS),
...
}
通过函数rockchip_clk_register_branches()
完成进行各功能的例化并完成时钟注册。
rockchip_clk_register_branches(ctx, rk3399_clk_branches,
ARRAY_SIZE(rk3399_clk_branches));
3.3 其他时钟控制项
- Tips 1
以上所有的时钟完成注册后,会将时钟信息更新进RK3399的时钟信息表,如下:
void rockchip_clk_add_lookup(struct rockchip_clk_provider *ctx,
struct clk *clk, unsigned int id)
{
if (ctx->clk_data.clks && id)
ctx->clk_data.clks[id] = clk;
}
- Tips 2
若希望上电之后某些时钟常开,可以配置数据结构rk3399_cru_critical_clocks
,例如:
static const char *const rk3399_cru_critical_clocks[] __initconst = {
"aclk_usb3_noc",
"aclk_gmac_noc",
"pclk_gmac_noc",
"pclk_center_main_noc",
"aclk_cci_noc0",
"aclk_cci_noc1",
"clk_dbg_noc",
...
}
Tips 3
打印RK3399的时钟树(信息很多),可以查看当前环境下所有的时钟信息,例如:
[root@rk3399:/]# cat /sys/kernel/debug/clk/clk_summary
clock enable_cnt prepare_cnt rate accuracy phase
----------------------------------------------------------------------------------------
rk808-clkout2 0 0 32768 0 0
xin32k 0 0 32768 0 0
CLK_CAMERA_24MHZ 0 0 24000000 0 0
ap6256_lpo_clk 2 2 32768 0 0
clkin_gmac 1 1 125000000 0 0
clk_rmii_src 4 4 125000000 0 0
clk_rmii_tx 2 2 125000000 0 0
clk_rmii_rx 1 1 125000000 0 0
clk_mac_ref 1 1 125000000 0 0
clk_mac_refout 1 1 125000000 0 0
dummy_vpll 0 0 0 0 0
dummy_cpll 0 0 0 0 0
clk_test_pre 0 0 0 0 0
clk_test 0 0 0 0 0
clk_test_frac 0 0 0 0 0
clk_cifout_src 0 0 0 0 0
clk_testout2_pll_src 0 0 0 0 0
clk_testout1_pll_src 0 0 0 0 0
......
END
我的微信