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 形式展示了时钟提供者节点的完整结构,包括属性、输入输出关系以及下游设备的引用方式:
4.6 内核解析流程简述
当内核启动并解析设备树时,时钟提供者节点的处理流程如下:
- 节点匹配: 内核遍历设备树,对每个节点调用
of_clk_init(),根据compatible字符串匹配对应的时钟驱动。 - 属性读取: 驱动读取
#clock-cells确定输出时钟数量,读取clock-output-names获取时钟名称列表,读取clock-frequency获取初始频率。 - 时钟注册: 驱动调用
clk_register()系列函数注册时钟,并通过of_clk_add_provider()将节点与时钟句柄关联。 - 消费者解析: 当下游设备驱动调用
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 是否匹配驱动,以及驱动是否成功加载 |