4. 时钟提供者节点配置:clocks、clock-output-names、#clock-cells 属性详解与配置示例

在设备树中,时钟提供者(Clock Provider)是产生时钟信号的硬件模块,例如 PLL、晶振振荡器、时钟门控单元或 SoC 内部的时钟树根节点。正确配置时钟提供者节点,是下游设备(Consumer)能够获取并正确使用时钟信号的前提。本节将深入解析三个核心属性:#clock-cells、clock-output-names 以及 clock-frequency,并通过完整示例展示如何构建一个标准的时钟提供者节点。

核心概念: 时钟提供者节点通过 #clock-cells 声明其输出时钟的索引方式,通过 clock-output-names 为每个输出时钟命名,通过 clock-frequency 指定固定频率时钟的初始值。这三者共同构成了内核解析时钟拓扑的元数据基础。

4.1 属性详解与对比

下表从功能、语法、使用场景三个维度对比了时钟提供者节点的三个核心属性:

属性名 功能描述 语法格式 是否必需 典型场景
#clock-cells 定义时钟提供者输出时钟的索引单元数量。值为 0 表示单输出时钟;值为 1 表示多输出时钟,需通过索引号区分。 #clock-cells = <0> 或 <1> 是 固定频率晶振(0);PLL/时钟门控(1)
clock-output-names 为每个输出时钟指定一个可读的字符串名称,便于调试和内核日志识别。 clock-output-names = "clk1", "clk2", ... 推荐 多输出时钟节点,如 SoC 时钟控制器
clock-frequency 指定固定频率时钟的初始频率值,单位 Hz。通常用于晶振或不可编程时钟源。 clock-frequency = <25000000> 固定频率时钟必需 外部晶振、RC 振荡器
避坑指南: 当 #clock-cells = <0> 时,下游设备引用该时钟时不需要提供索引参数,即 clocks = <&clk_provider>。当 #clock-cells = <1> 时,必须提供索引,如 clocks = <&clk_provider 2>。索引值从 0 开始计数。

4.2 配置示例:固定频率晶振节点

固定频率晶振是最简单的时钟提供者,通常只有一个输出时钟,且频率不可编程。以下是一个 24MHz 晶振的完整设备树节点配置:

/* 固定频率晶振节点:24MHz */
osc24M: oscillator {
    compatible = "fixed-clock";
    #clock-cells = <0>;          /* 单输出时钟,无需索引 */
    clock-frequency = <24000000>; /* 24 MHz */
    clock-output-names = "osc24M"; /* 可选,便于调试 */
};

解析:

  • compatible = "fixed-clock":内核通过此字符串匹配到 drivers/clk/clk-fixed-rate.c 驱动。
  • #clock-cells = <0>:声明该节点只输出一个时钟信号,下游引用时无需提供索引。
  • clock-frequency = <24000000>:驱动读取此属性,注册一个频率为 24MHz 的固定时钟。
  • clock-output-names = "osc24M":内核注册时钟时,将 clk 名称设为 "osc24M",便于 /sys/kernel/debug/clk/osc24M 查看。

4.3 配置示例:多输出时钟控制器节点

SoC 内部的时钟控制器通常管理多个时钟输出,例如 PLL、分频器、门控等。以下是一个具有 4 个输出时钟的控制器节点示例:

/* SoC 时钟控制器:输出 4 路时钟 */
clk_ctrl: clock-controller@ff760000 {
    compatible = "vendor,soc-clk-controller";
    reg = <0xff760000 0x1000>;
    #clock-cells = <1>;          /* 多输出时钟,需索引 */
    clocks = <&osc24M>;          /* 输入参考时钟 */
    clock-output-names = "cpu_clk", "bus_clk", "periph_clk", "dma_clk";
    /* 索引 0: cpu_clk, 索引 1: bus_clk, 索引 2: periph_clk, 索引 3: dma_clk */
};

解析:

  • #clock-cells = <1>:声明该节点输出多个时钟,下游设备必须通过索引号指定具体时钟。
  • clock-output-names:定义了 4 个时钟名称,顺序与索引号一一对应。索引 0 对应 "cpu_clk",索引 1 对应 "bus_clk",以此类推。
  • clocks = <&osc24M>:该控制器本身也是一个时钟消费者,它需要外部晶振作为输入参考源。
重要约束: clock-output-names 的字符串数量必须与 #clock-cells 隐含的输出时钟数量一致。对于 #clock-cells = <1>,驱动通常通过解析设备树或硬件寄存器来确定实际输出数量,但名称列表长度应与之匹配,否则可能导致内核注册时钟时名称错位或数组越界。

4.4 下游设备引用示例

配置好时钟提供者后,下游设备通过 clocks 属性引用时钟。以下展示了两种引用方式:

/* 引用单输出时钟(晶振) */
uart0: serial@ff000000 {
    compatible = "vendor,uart";
    reg = <0xff000000 0x100>;
    clocks = <&osc24M>;          /* 直接引用,无需索引 */
    clock-names = "baud";         /* 可选,指定时钟角色 */
};

/* 引用多输出时钟(时钟控制器) */
eth0: ethernet@ff100000 {
    compatible = "vendor,ethernet";
    reg = <0xff100000 0x1000>;
    clocks = <&clk_ctrl 1>, <&clk_ctrl 2>; /* 索引 1: bus_clk, 索引 2: periph_clk */
    clock-names = "bus", "periph";
};

引用规则:

  • 当 #clock-cells = <0> 时,clocks 属性中只需提供 &provider 句柄。
  • 当 #clock-cells = <1> 时,clocks 属性中每个条目必须包含 &provider 和一个整数索引,如 &clk_ctrl 1。
  • clock-names 为可选属性,用于为每个时钟指定语义名称,驱动可通过 devm_clk_get(dev, "bus") 获取对应时钟。

4.5 时钟提供者节点结构总览

下图以 SVG 形式展示了时钟提供者节点的完整结构,包括属性、输入输出关系以及下游设备的引用方式:

时钟提供者节点结构总览 时钟提供者节点结构总览 固定频率晶振 compatible = "fixed-clock" #clock-cells = <0> clock-frequency = <24M> clock-output-names = "osc24M" 时钟控制器 compatible = "vendor,soc-clk" #clock-cells = <1> clock-output-names = "cpu_clk", "bus_clk", ... clocks = <&osc24M> 下游设备 (Consumer) clocks = <&osc24M> clocks = <&clk_ctrl 1> clock-names = "bus" 参考时钟 直接引用 索引引用 关键说明: 1. #clock-cells = <0>:单输出时钟,下游引用时无需索引参数。 2. #clock-cells = <1>:多输出时钟,下游引用时必须提供整数索引。 3. clock-output-names 顺序与索引号一一对应,数量必须匹配。 4. clock-frequency 仅用于固定频率时钟,可编程时钟由驱动动态设置。 5. 时钟控制器本身也可以是消费者,通过 clocks 属性引用外部晶振。 6. 内核通过 of_clk_add_provider() 注册提供者,通过 of_clk_get() 解析消费者引用。 7. 调试接口:/sys/kernel/debug/clk/ 下可查看所有注册的时钟树。

4.6 内核解析流程简述

当内核启动并解析设备树时,时钟提供者节点的处理流程如下:

  1. 节点匹配: 内核遍历设备树,对每个节点调用 of_clk_init(),根据 compatible 字符串匹配对应的时钟驱动。
  2. 属性读取: 驱动读取 #clock-cells 确定输出时钟数量,读取 clock-output-names 获取时钟名称列表,读取 clock-frequency 获取初始频率。
  3. 时钟注册: 驱动调用 clk_register() 系列函数注册时钟,并通过 of_clk_add_provider() 将节点与时钟句柄关联。
  4. 消费者解析: 当下游设备驱动调用 devm_clk_get() 时,内核通过 of_clk_get() 解析 clocks 属性,根据提供者句柄和索引返回对应的 struct clk 指针。
调试技巧: 在 /sys/kernel/debug/clk/clk_summary 中可以查看所有已注册时钟的树状结构、当前频率、启用计数等信息。如果时钟提供者节点配置错误,该文件将缺失对应的时钟条目。

4.7 常见配置错误与排查

错误现象 可能原因 解决方案
内核报错 OF: failed to get clock 下游设备 clocks 属性中索引号超出提供者输出范围 检查 #clock-cells 值与索引号是否匹配
时钟名称显示为 unnamed 未设置 clock-output-names 或名称数量不匹配 添加 clock-output-names 并确保数量正确
固定频率时钟频率为 0 未设置 clock-frequency 或格式错误 检查 clock-frequency 属性值,确保为整数且单位 Hz
驱动 probe 失败,返回 -EPROBE_DEFER 时钟提供者尚未注册,消费者被延迟探测 检查提供者节点 compatible 是否匹配驱动,以及驱动是否成功加载