瑞芯微Rockchip开发者社区
直播中

xymbmcu

12年用户 1082经验值
擅长:可编程逻辑
私信 关注
[问答]

如何实现RK3399时钟系统?

rockchip的时钟系统整体结构是怎样的?
kernel中的时钟初始化方式有哪些?
如何实现RK3399时钟系统?

回帖(1)

康根

2022-3-8 11:14:34
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文件在前文已经描述过:



Linux时钟系统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_u***2phy0_ref", "xin24m", CLK_IGNORE_UNUSED,RK3399_CLKGATE_CON(6), 5, GFLAGS),
        GATE(SCLK_USB2PHY1_REF, "clk_u***2phy1_ref", "xin24m", CLK_IGNORE_UNUSED,RK3399_CLKGATE_CON(6), 6, GFLAGS),
        GATE(SCLK_USBPHY0_480M_SRC, "clk_u***phy0_480m_src", "clk_u***phy0_480m", 0,RK3399_CLKGATE_CON(13), 12, GFLAGS),
        GATE(SCLK_USBPHY1_480M_SRC, "clk_u***phy1_480m_src", "clk_u***phy1_480m", 0,RK3399_CLKGATE_CON(13), 12, GFLAGS),
        MUX(0, "clk_u***phy_480m", mux_u***phy_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_u***3_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
......
举报

更多回帖

发帖
×
20
完善资料,
赚取积分