3. 时钟提供者节点配置:如何定义一个时钟控制器节点

时钟提供者(Clock Provider)节点是设备树中描述硬件时钟发生器(如 PLL、OSC、MUX、DIV)的核心单元。一个正确的时钟控制器节点定义,是驱动能够通过 clk_get / of_clk_get 获取时钟源、并正确配置时钟树的前提。本节将详细拆解时钟提供者节点的标准属性、配置规范及常见错误。

3.1 时钟提供者节点的核心属性

一个完整的时钟控制器节点通常包含以下三类属性:标识属性、输出描述属性、控制参数属性。下表列出了最关键的属性及其用途:

属性名 类型 是否必需 说明
#clock-cells u32 是 指定时钟输出索引所需的 cell 数量。0 表示单输出;1 表示多输出,需通过 phandle + 索引引用。
clock-output-names stringlist 推荐 为每个时钟输出指定名称,便于驱动和调试时识别。
compatible string 是 匹配对应时钟驱动,格式通常为 "vendor,device"。
reg prop-encoded-array 是 时钟控制器的 MMIO 地址空间。
clocks phandle list 条件必需 该控制器的输入时钟源(如晶振、上一级 PLL)。
assigned-clocks phandle list 可选 在系统启动阶段由内核统一配置的时钟列表。
assigned-clock-rates prop-encoded-array 可选 与 assigned-clocks 对应,指定目标频率。
关键原则:#clock-cells 的值直接决定了消费者节点引用该时钟时的语法。若设为 0,消费者只需 clocks = &clk_provider;;若设为 1,则必须提供索引,如 clocks = &clk_provider, 0;。

3.2 标准时钟控制器节点示例

以下是一个典型的单输出时钟控制器节点定义,适用于固定频率振荡器或单一 PLL:

osc24M: osc24M {
    compatible = "fixed-clock";
    #clock-cells = <0>;
    clock-frequency = <24000000>;
    clock-output-names = "osc24M";
};

对于多输出时钟控制器(如 SoC 内部的 CCU),需要为每个输出分配索引和名称:

ccu: clock-controller@1000000 {
    compatible = "vendor,soc-ccu";
    reg = <0x01000000 0x1000>;
    #clock-cells = <1>;
    clocks = <&osc24M>;
    clock-output-names = "cpu_clk", "ahb_clk", "apb_clk", "uart_clk";
    assigned-clocks = <&ccu 0>, <&ccu 1>;
    assigned-clock-rates = <800000000>, <200000000>;
};
常见陷阱:clock-output-names 的顺序必须与硬件输出序号严格对应。例如,索引 0 对应 "cpu_clk",索引 1 对应 "ahb_clk",不能颠倒。否则驱动通过索引获取时钟时,名称与硬件不匹配,将导致调试信息混乱。

3.3 #clock-cells 的语义与选择

#clock-cells 是时钟提供者节点最重要的属性,它决定了时钟输出模型的复杂度。下表对比了两种常见取值的使用场景:

#clock-cells 值 适用场景 消费者引用示例 驱动侧处理
0 单输出时钟(固定振荡器、单一 PLL) clocks = &osc24M; 驱动直接注册一个 clk_hw,无需索引解析
1 多输出时钟(CCU、时钟树根节点) clocks = <&ccu 2>; 驱动需根据 cell 中的索引返回对应 clk_hw
2 复杂时钟(带域/组的多级输出) clocks = <&complex_clk 1 3>; 驱动需解析两个 cell,通常用于分层时钟域

3.4 clock-output-names 的调试价值

clock-output-names 虽然不是内核强制要求的属性,但在调试阶段具有不可替代的作用。当驱动通过 of_clk_get_parent_name 或 clk_hw_get_name 获取时钟名称时,该属性直接决定了 /sys/kernel/debug/clk 下显示的时钟树节点名称。

以下是一个实际调试场景中的对比:

  • 未定义 clock-output-names: 内核自动生成名称如 clock-controller@1000000-2,难以与硬件对应。
  • 定义了 clock-output-names: 显示为 uart_clk,一眼可识别。
最佳实践: 即使硬件只有单输出,也建议显式定义 clock-output-names。在调试时钟树时,有意义的名称能节省大量排查时间。

3.5 时钟提供者节点配置流程

下图展示了从硬件规格到设备树节点定义的标准流程:

时钟提供者节点配置流程 1. 分析硬件规格 2. 确定输出数量 3. 设置 #clock-cells 4. 命名每个输出 5. 配置输入时钟 6. 添加 assigned 属性 7. 编写 compatible/reg 8. 编译 & 验证 完成:可被消费者引用的时钟节点

3.6 驱动侧与设备树的匹配机制

时钟提供者节点最终需要与内核驱动匹配。下图展示了从设备树解析到驱动注册的完整链路:

设备树 → 驱动匹配链路 设备树节点 compatible = "vendor,ccu" of_match_table .compatible = "vendor,ccu" probe() 执行 解析 #clock-cells clk_hw_register() 注册到时钟框架 消费者通过 phandle 引用

3.7 常见配置错误与排查清单

在定义时钟提供者节点时,以下错误最为常见,建议在代码审查和调试时逐一核对:

  • #clock-cells 与实际输出数量不匹配: 若硬件有 3 路输出却设为 0,消费者无法通过索引区分;若设为 1 但驱动只注册了 1 个时钟,消费者引用索引 1 时将返回 NULL。
  • clock-output-names 顺序错乱: 名称列表与硬件输出索引不对应,导致调试时看到错误的时钟名称。
  • 缺少 clocks 输入属性: 对于非根时钟控制器,若未声明输入时钟源,驱动在注册时钟树时可能因父时钟缺失而失败。
  • assigned-clocks 与 assigned-clock-rates 长度不匹配: 两个列表的元素数量必须一致,否则内核会跳过整个配置。
  • compatible 字符串拼写错误: 与驱动中 of_match_table 定义的字符串必须完全一致,包括大小写和标点。
调试检查命令: 在系统启动后,执行 ls /sys/kernel/debug/clk/ 查看所有已注册时钟的名称,确认与 clock-output-names 定义一致。若发现名称缺失或异常,优先检查设备树解析是否成功。

3.8 本章小结

时钟提供者节点的配置是设备树时钟子系统的基础。核心要点包括:

  • #clock-cells 决定输出模型,必须与硬件实际输出数量一致。
  • clock-output-names 虽非强制,但强烈推荐用于提升调试效率。
  • 输入时钟 clocks 和初始化属性 assigned-clocks / assigned-clock-rates 是构建完整时钟树的关键。
  • 配置错误通常表现为消费者获取时钟失败或时钟树名称异常,通过 debugfs 可快速定位。