节。
在本篇内容中,我们将围绕 Linux 内核中的时钟子系统焦点架构 —— Common Clock Framework(简称 CCF)睁开深入解说,目标是帮助你全面理解其设计理念、重要数据结构、注册流程、驱动实现方式,以及怎样基于 NXP i.MX8MP 平台进行完整的时钟驱动开发。
一、Common Clock Framework 简介
Common Clock Framework(CCF)是 Linux 内核自 v3.4 起引入的通用时钟架构,用于统一管理 SoC 上所有的时钟资源,解决平台异构、驱动分裂、代码冗余等问题。
CCF 的设计目标是:
- 提供统一的时钟抽象接口(如 clk_get() / clk_prepare() / clk_enable())。
- 支持复杂的时钟树结构,包括分频、门控、复用器等组件。
- 支持时钟源的动态切换与频率动态调整(DFS)。
- 解耦装备驱动与时钟控制的实现。
二、CCF 的三大焦点脚色与架构总览
在上一篇中我们已概述三个焦点脚色:
脚色作用示例时钟提供者(Provider)注册并实现时钟控制逻辑,如 PLL、分频器、门控器NXP CCM 时钟控制器驱动时钟消费者(Consumer)使用时钟接口获取和启用时钟UART/I2C 等外设驱动时钟框架(Framework)统一管理所偶然钟,维护拓扑关系与接口drivers/clk/clk.c 架构示意图:
- +---------------------+ +-------------------+
- | UART 驱动 (Consumer)|<---->| CCF 框架层 |
- +---------------------+ +-------------------+
- ↑
- |
- +---------------------+
- | 时钟控制器驱动 |
- | (如 fsl-imx8mp-ccm) |
- +---------------------+
复制代码
三、CCF 焦点数据结构分析
1. struct clk_hw
该结构体表示一个底层时钟硬件实体,由时钟提供者实现:
- struct clk_hw {
- struct clk_core *core;
- struct clk_init_data *init;
- // 可扩展的私有数据指针
- };
复制代码 2. struct clk_ops
用于定义操作时钟的函数集,是最关键的回调接口:
- struct clk_ops {
- int (*enable)(struct clk_hw *hw);
- void (*disable)(struct clk_hw *hw);
- unsigned long (*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate);
- long (*round_rate)(struct clk_hw *hw, unsigned long rate,
- unsigned long *parent_rate);
- int (*set_rate)(struct clk_hw *hw, unsigned long rate,
- unsigned long parent_rate);
- ...
- };
复制代码 3. struct clk_init_data
用于注册时钟时的初始化信息:
- struct clk_init_data {
- const char *name;
- const struct clk_ops *ops;
- const char * const *parent_names;
- u8 num_parents;
- unsigned long flags;
- };
复制代码 四、时钟提供者(Provider)注册流程
Provider 通过 clk_register() 或 devm_clk_hw_register() 将 clk_hw 实例注册给框架,框架内部建立 clk_core 对象并添加至全局时钟树。
示例流程:
- 定义 clk_ops 实现;
- 构造 clk_hw;
- 填写 clk_init_data;
- 调用 devm_clk_hw_register() 完成注册;
- 若是 platform 装备,通过 of_clk_add_hw_provider() 提供给装备树接口。
五、实战开发:NXP i.MX8MP 时钟驱动剖析
我们以 NXP i.MX8MP 的 UART2 根时钟 IMX8MP_CLK_UART2_ROOT 为例,解说从时钟控制器提供者,到 UART2 驱动消费者的完整链路。
1. 装备树设置分析
- uart2: serial@30890000 {
- compatible = "fsl,imx8mp-uart", "fsl,imx6q-uart";
- reg = <0x30890000 0x10000>;
- interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
- clocks = <&clk IMX8MP_CLK_UART2_ROOT>,
- <&clk IMX8MP_CLK_UART2_ROOT>;
- clock-names = "ipg", "per";
- status = "disabled";
- };
复制代码 <&clk IMX8MP_CLK_UART2_ROOT> 表示装备节点引用了 ID 为 IMX8MP_CLK_UART2_ROOT 的时钟,provider 必须实现它。
2. Provider 实现(drivers/clk/imx/clk-imx8mp.c)
- static const struct imx8m_clk_root_clk uart2_root = {
- .id = IMX8MP_CLK_UART2_ROOT,
- .name = "uart2_root_clk",
- .parent_names = (const char *[]){ "uart2_div" },
- .num_parents = 1,
- .flags = CLK_SET_RATE_PARENT,
- };
复制代码 注册流程:
- hw = imx8m_clk_hw_register_composite(NULL, clk_root->name,
- clk_root->parent_names, clk_root->num_parents,
- mux_hw, &clk_mux_ops,
- rate_hw, &clk_divider_ops,
- gate_hw, &clk_gate_ops,
- clk_root->flags);
复制代码 注: 每个 root clock 通常由一个复用器、一个分频器和一个门控组成,符合 CCF 的复适时钟模子。
六、时钟消费者怎样使用时钟
驱动中使用以下接口获取与控制时钟:
- struct clk *clk = devm_clk_get(dev, "ipg");
- clk_prepare_enable(clk);
- ...
- clk_disable_unprepare(clk);
复制代码 内核根据 clock-names 字符串与装备树中的 <&clk ID> 找到实际的 clk_core 实例,进一步调用对应 clk_ops 接口。
七、完整代码实例:模仿一个外设时钟 Provider
以下模仿一个名为 "demo-gate-clk" 的时钟,基于 GPIO 控制实现一个简单门控时钟:
1. 示例平台驱动结构:
- static struct clk_hw *demo_clk_hw;
- static struct clk_ops demo_clk_ops = {
- .enable = demo_clk_enable,
- .disable = demo_clk_disable,
- };
- static int demo_clk_probe(struct platform_device *pdev)
- {
- struct clk_init_data init;
- demo_clk_hw = devm_kzalloc(&pdev->dev, sizeof(*demo_clk_hw), GFP_KERNEL);
- init.name = "demo-gate-clk";
- init.ops = &demo_clk_ops;
- init.num_parents = 0;
- init.flags = 0;
- demo_clk_hw->init = &init;
- clk_hw_register(&pdev->dev, demo_clk_hw);
- of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_simple_get, demo_clk_hw);
- return 0;
- }
复制代码 2. 在装备树中形貌该时钟:
- demo_clk: demo-clk {
- compatible = "vendor,demo-gate-clk";
- #clock-cells = <0>;
- };
复制代码 3. 在其他装备中消费该时钟:
- my_peripheral@12340000 {
- ...
- clocks = <&demo_clk>;
- clock-names = "core";
- };
复制代码 八、Framework 的统一管理机制
Linux 内核 CCF 框架通过 clk_core 全局对象池,维持整个时钟拓扑结构,并提供统一接口进行操作与调试。
焦点文件位于 drivers/clk/clk.c,负责:
- 初始化时钟树结构;
- 实现 clk_get()、clk_set_rate() 等函数;
- 管理依靠关系(父子时钟);
- 调度跨时钟域变动时的同步机制。
框架的调试接口如:
- cat /sys/kernel/debug/clk/clk_summary
复制代码 可查看当前系统所偶然钟状态。
九、总结与实战建议
- Provider 注册逻辑必须按 CCF 规则实现 clk_hw、clk_ops,并注册给框架;
- Consumer 驱动应只依靠尺度 clk_*() 接口,不直接操作硬件;
- Framework 负责维护拓扑、管理父子关系、支持动态调频调占;
- 实战中,先梳理平台提供的 clock ID 定义(如 imx8mp-clock.h),再定位 clock controller 驱动;
- 开发自定义外设或 FPGA 时钟控制时,也可编写 Provider 兼容装备树接口。
十、每日提问与答案
问题 1:一个时钟可以有多个父时钟吗?怎样选择?
回答:
可以。CCF 支持多父时钟的复用器机制,注册 clk_hw 时传入多个 parent_names,并实现 set_parent() 和 get_parent() 回调。使用者可调用 clk_set_parent() 选择目标父时钟。
问题 2:怎样调试某个时钟未启用的问题?
回答:
可通过以下方式定位:
- cat /sys/kernel/debug/clk/clk_summary
查看时钟是否启用;
- 查看是否被 Consumer 使用并调用 clk_prepare_enable();
- 查抄 Provider 的 clk_ops.enable() 是否被成功实行;
- 验证时钟的 parent 是否启用,是否存在 gating 问题。
以上即为 Day 27 下篇全部内容,完整解说了 CCF 架构设计与实际使用方式。
下一日我们将正式进入子系统与电源管理集成的驱动开发专题。
视频教程请关注 B 站:“嵌入式 Jerry”
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |