嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > Linux kernel中的CPU时钟管理实例篇-(3) | 基于RK3399

Linux kernel中的CPU时钟管理实例篇-(3) | 基于RK3399

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这个宏定义,在时钟驱动中通过它来声明初始化函数,其实现原理如下图所示:

CLK_OF_DECLARE

3. RK3399时钟系统实现

rockchip为全系列芯片提供了统一的时钟初始化API,这一点非常值得我们借鉴同时也是很好的一种代码架构模式。RK3399的时钟初始化涉及两部分内容,分别是crupmu,二者原理相通,本文以cru初始化为例。具体包括:

RK3399时钟初始化涉及到的文件

上图中涉及到的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@ff770000pmugrf: syscon@ff320000定义了grfpmugrf,包含了时钟控制相关的寄存器。
通过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,如下图所示:

Fractional PLL

在代码中使用数据结构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), 8310, rk3399_pll_rates),
 [bpll] = PLL(pll_rk3399, PLL_APLLB, "bpll", mux_pll_p, 0, RK3399_PLL_CON(8),RK3399_PLL_CON(11), 8310, rk3399_pll_rates),
 [dpll] = PLL(pll_rk3399, PLL_DPLL, "dpll", mux_pll_p, 0, RK3399_PLL_CON(16),RK3399_PLL_CON(19), 8310NULL),
#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), 8310, rk3399_pll_rates),
#else
 [cpll] = PLL(pll_rk3399, PLL_CPLL, "cpll", mux_pll_p, 0, RK3399_PLL_CON(24),RK3399_PLL_CON(27), 831, 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), 8310, rk3399_pll_rates),
 [npll] = PLL(pll_rk3399, PLL_NPLL, "npll",  mux_pll_p, 0, RK3399_PLL_CON(40),RK3399_PLL_CON(43), 831, 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), 8310, 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), 61, 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

我的微信

--- 往期精彩内容 ---