Linux内核时钟子系统实战:手把手教你用CCF框架配置RK3399的PLL时钟
Linux内核时钟子系统实战:RK3399平台CCF框架深度配置指南
1. 嵌入式系统时钟架构核心解析
时钟系统如同SoC的神经系统,精确协调着每个功能模块的工作节奏。在RK3399这类高性能嵌入式平台中,时钟管理直接关系到系统稳定性、功耗表现和外设可靠性。传统裸机开发中直接操作寄存器的方式,在Linux环境下已被Common Clock Framework(CCF)所取代,这套标准化接口为开发者提供了更安全的时钟控制途径。
现代SoC时钟树通常呈现多级联结构,以RK3399为例,其典型时钟拓扑包含:
- 时钟源层:24MHz晶振作为基础时钟源
- PLL层:GPLL/CPLL/NPLL等锁相环进行频率合成
- 分配层:通过分频器、多路选择器生成各模块所需时钟
- 门控层:最终控制时钟是否送达具体外设
// RK3399典型时钟路径示例
OSC(24MHz) -> GPLL(594MHz) -> ACLK_PERIHP(150MHz) -> HCLK_PERIHP(75MHz)
CCF框架将这些硬件特性抽象为六类时钟设备:
| 时钟类型 | 功能描述 | 典型应用场景 |
|---|---|---|
| Fixed rate | 固定频率时钟源 | 外部晶振、RTC时钟 |
| Gate | 简单开关控制的时钟 | 外设时钟使能控制 |
| Divider | 可配置分频系数的时钟 | 总线时钟分频 |
| Mux | 多路输入的时钟选择器 | CPU动态调频切换 |
| Fixed factor | 固定倍频/分频关系的时钟 | PLL后级固定分频 |
| Composite | 组合多个基本类型的复合时钟 | 复杂时钟路径管理 |
提示:通过
cat /sys/kernel/debug/clk/clk_summary可实时查看当前时钟树状态,这是调试时钟问题的第一工具。
2. RK3399时钟设备树深度配置
设备树作为硬件描述的核心载体,在时钟系统配置中扮演着关键角色。Rockchip系列芯片使用CRU(Clock and Reset Unit)作为时钟管理模块,RK3399的设备树配置需特别注意以下几个关键部分:
基础CRU节点定义:
cru: clock-controller@ff760000 {
compatible = "rockchip,rk3399-cru";
reg = <0x0 0xff760000 0x0 0x1000>;
rockchip,grf = <&grf>;
#clock-cells = <1>;
#reset-cells = <1>;
};
关键配置参数解析:
#clock-cells = 1:表示每个时钟输出需要1个参数标识assigned-clocks:声明需要初始化的时钟列表assigned-clock-rates:对应时钟的初始化频率值
PLL时钟配置实战:
assigned-clocks = <&cru PLL_GPLL>, <&cru PLL_CPLL>;
assigned-clock-rates = <594000000>, <800000000>;
常见问题解决方案:
-
时钟频率不生效:
- 检查CRU寄存器映射地址是否正确
- 确认时钟ID在头文件中的定义与dts一致
- 使用
clk_summary查看实际时钟频率
-
时钟门控异常:
# 调试时钟使能状态 grep "clk_gate" /sys/kernel/debug/clk/clk_summary -
时钟切换失败:
- 验证父时钟是否存在且已使能
- 检查mux切换的index是否有效
注意:修改PLL频率时需确保无设备正在使用该时钟,建议在系统初始化阶段完成主要PLL配置。
3. CCF驱动开发关键实现
时钟驱动开发的核心在于正确实现clk_ops结构体中的回调函数,以下以RK3399的PLL时钟为例展示关键实现步骤:
1. 定义硬件操作结构体:
static const struct clk_ops rockchip_rk3399_pll_clk_ops = {
.enable = rockchip_pll_enable,
.disable = rockchip_pll_disable,
.recalc_rate = rockchip_pll_recalc_rate,
.round_rate = rockchip_pll_round_rate,
.set_rate = rockchip_pll_set_rate,
};
2. 初始化clk_hw结构:
struct rockchip_clk_pll *pll;
struct clk_init_data init;
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
init.name = "pll_gpll";
init.ops = &rockchip_rk3399_pll_clk_ops;
init.parent_names = &parent_name;
init.num_parents = 1;
init.flags = CLK_GET_RATE_NOCACHE;
pll->hw.init = &init;
pll->type = pll_type;
pll->reg_base = reg_base;
3. 注册时钟到CCF框架:
ret = clk_hw_register(NULL, &pll->hw);
if (ret) {
kfree(pll);
return ERR_PTR(ret);
}
of_clk_add_hw_provider(np, of_clk_hw_simple_get, &pll->hw);
关键函数实现要点:
set_rate:需包含PLL锁定检测机制recalc_rate:考虑分频器、倍频器当前配置enable/disable:处理PLL上电序列和旁路模式
调试技巧:
# 跟踪时钟操作调用栈
echo 1 > /sys/kernel/debug/tracing/events/clk/enable
cat /sys/kernel/debug/tracing/trace_pipe
4. 时钟消费者驱动开发实践
设备驱动中使用时钟的标准流程包含获取、配置、使能三个关键阶段,以下是I2C控制器驱动中的典型应用:
1. 设备树时钟声明:
i2c1: i2c@ff110000 {
clocks = <&cru SCLK_I2C1>, <&cru PCLK_I2C1>;
clock-names = "i2c", "pclk";
};
2. 驱动中获取时钟资源:
struct i2c_controller {
struct clk *sclk;
struct clk *pclk;
};
static int i2c_rk3399_probe(struct platform_device *pdev)
{
struct i2c_controller *ctrl;
ctrl->sclk = devm_clk_get(&pdev->dev, "i2c");
if (IS_ERR(ctrl->sclk)) {
return PTR_ERR(ctrl->sclk);
}
ctrl->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(ctrl->pclk)) {
return PTR_ERR(ctrl->pclk);
}
}
3. 时钟使能最佳实践:
ret = clk_prepare_enable(ctrl->sclk);
if (ret) {
dev_err(&pdev->dev, "Could not prepare clock\n");
return ret;
}
/* 配置工作频率 */
ret = clk_set_rate(ctrl->sclk, 100000000);
if (ret) {
dev_warn(&pdev->dev, "Unable to set clock rate\n");
}
常见问题处理方案:
-
时钟资源获取失败:
- 检查dts中clock-names与驱动中字符串完全匹配
- 确认时钟提供者已正确初始化
-
频率设置超出范围:
// 安全设置频率模式 rounded_rate = clk_round_rate(clk, target_rate); if (rounded_rate != target_rate) { dev_warn(dev, "Requested %lu, got %lu\n", target_rate, rounded_rate); } clk_set_rate(clk, rounded_rate); -
时钟状态管理:
- 使用
clk_prepare/clk_unprepare管理资源分配 - 在电源管理回调中正确处理时钟开关
- 使用
在实际项目中,我们发现RK3399的I2C时钟配置存在一个硬件限制:当PCLK频率超过50MHz时,需要额外配置CRU中的分频寄存器。这提醒我们在使用CCF框架时,仍需关注芯片特定的硬件约束条件。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)