3. 设备树中的时钟绑定:clocks、clock-names、clock-frequency 属性解析

设备树里的时钟绑定,说白了就是告诉内核:我这个外设需要几路时钟、每路时钟叫什么名字、频率是多少。我刚开始接触设备树时,总觉得这些属性是随便填的,直到有一次调一个 USB 控制器,死活枚举不到设备……后来发现是 clock-names 写错了,驱动里拿不到正确的时钟句柄。

嗯,咱们今天就把这三个属性彻底讲透。

3.1 clocks 属性:时钟的“接线图”

clocks 属性是一个 phandle + 时钟索引 的列表。每个条目指向一个时钟提供者(clock provider),并附上该提供者内部的时钟索引。

格式很简单:

clocks = <phandle1 index1>, <phandle2 index2>, ...;

举个例子:

uart0: serial@10000000 {
    compatible = "my-uart";
    clocks = <&clkc 10>, <&clkc 20>;
    clock-names = "baud", "pclk";
};

这里 &clkc 10 表示:使用 clkc 这个时钟控制器输出的第 10 号时钟。&clkc 20 同理。驱动里通过 clk_get(dev, "baud") 就能拿到第 0 个时钟,通过 clk_get(dev, "pclk") 拿到第 1 个时钟。

关键点:phandle 指向的节点必须实现 #clock-cells 属性。如果 #clock-cells = <1>,则 phandle 后面必须跟一个索引;如果 #clock-cells = <0>,则 phandle 后面不跟索引。

3.2 clock-names 属性:给时钟起个名字

clock-names 是 clocks 属性的“标签”。它让驱动代码不再依赖索引顺序,而是通过名字来获取时钟。

我见过不少新手直接写:

clocks = <&clkc 10>, <&clkc 20>;
// 没有 clock-names

然后驱动里用 clk_get(dev, NULL) 或者硬编码索引。这样一旦设备树里调整了顺序,驱动就崩了。我曾经在一个项目里吃过这个亏——硬件工程师改了时钟树,我忘了同步更新驱动,结果花了半天定位问题。

我的习惯:只要 clocks 属性里超过一个条目,就一定配上 clock-names。这是低成本、高回报的好习惯。

3.3 clock-frequency 属性:固定频率的“快捷方式”

clock-frequency 是一个可选的属性,用于直接指定时钟频率,单位是 Hz。它通常用在两种场景:

  1. 固定频率的晶振:比如 24MHz 的晶振,不需要复杂的 PLL 配置。
  2. 驱动不想走 clk API:某些简单外设(比如早期的 SPI 控制器)直接读这个属性就能拿到频率,省去 clk_get/clk_round_rate 的调用。

示例:

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

注意:clock-frequency 是 固定值,不能动态调整。如果你的外设需要变频,还是得用 clocks + clock-names 走 clk 框架。

避坑:有些驱动同时检查 clocks 和 clock-frequency。如果两者都存在,驱动通常优先使用 clocks 提供的时钟。我曾经遇到一个网卡驱动,设备树里既配了 clocks 又写了 clock-frequency,结果驱动用了 clocks 的 50MHz,而硬件实际需要 25MHz——因为 clock-frequency 被忽略了。所以,别混用,选一种方式就好。

3.4 时钟索引与 phandle 的映射关系

咱们用一张表来总结这三者的关系:

属性 作用 是否必需 典型值
clocks 指定时钟提供者和索引 是(除非用 fixed-clock) <&clkc 10>, <&clkc 20>
clock-names 给每个时钟起名字 推荐(多时钟时必需) "baud", "pclk"
clock-frequency 直接指定频率(固定值) 可选 <24000000>

phandle 和索引的映射逻辑是这样的:

  • 驱动调用 clk_get(dev, "baud") → 内核在设备树里找到 clock-names 中 "baud" 对应的位置(第 0 个)→ 取出 clocks[0] 的 phandle 和索引 → 去 phandle 指向的节点获取时钟。
  • 如果 clock-names 不存在,驱动只能用 clk_get(dev, NULL) 或 of_clk_get(dev, index) 按索引取。

3.5 实战案例:一个 I2C 控制器的时钟绑定

咱们看一个完整的例子:

i2c0: i2c@10010000 {
    compatible = "my,i2c";
    reg = <0x10010000 0x1000>;
    interrupts = <31>;
    clocks = <&clkc 15>, <&clkc 30>;
    clock-names = "core", "bus";
    clock-frequency = <100000>;  // I2C 总线频率 100kHz
};

驱动里怎么用?

struct clk *core_clk, *bus_clk;
core_clk = clk_get(&pdev->dev, "core");
bus_clk = clk_get(&pdev->dev, "bus");
clk_prepare_enable(core_clk);
clk_prepare_enable(bus_clk);

// 读取 clock-frequency 作为 I2C 时序计算的参考
of_property_read_u32(pdev->dev.of_node, "clock-frequency", &i2c_freq);

这里 core 时钟给 I2C 内核逻辑用,bus 时钟给总线接口用。而 clock-frequency 是 I2C 协议层的速率,跟时钟树的频率是两码事——别搞混了。

3.6 知识结构图:时钟绑定的完整流程

下面这张图展示了从设备树到驱动获取时钟的完整链路:

设备树时钟绑定与驱动获取流程 设备树时钟绑定与驱动获取流程 设备树节点 clocks / clock-names OF 解析层 of_clk_get / of_property_read clk 框架 clk_get / clk_prepare 驱动层 clk_enable / clk_set_rate 硬件层 PLL / 晶振 / 分频器 clock-frequency 直接读取固定频率 关键说明: • clocks + clock-names 走 clk 框架,支持动态调频 • clock-frequency 是固定值,不走 clk 框架,适合简单外设

3.7 常见问题与避坑

我曾经踩过的坑:
  • phandle 写错:编译时不会报错,但内核解析时会返回 -EPROBE_DEFER,导致驱动加载延迟甚至失败。检查 /sys/kernel/debug/devices_deferred 可以定位。
  • clock-names 拼写不一致:设备树里写 "baud",驱动里用 "baudrate",结果 clk_get 返回 NULL。我建议在头文件里定义宏,比如 #define I2C_CLK_CORE "core",两边统一引用。
  • #clock-cells 不匹配:如果 clock provider 的 #clock-cells = <0>,但你在 clocks 里写了索引,内核会直接忽略索引值,但不会报错。这会导致你拿到的时钟不是预期的那个。
调试小技巧:在驱动里加几行打印,确认拿到的时钟句柄和频率:
struct clk *clk = clk_get(dev, "core");
if (IS_ERR(clk)) {
    dev_err(dev, "Failed to get core clock: %ld\n", PTR_ERR(clk));
} else {
    dev_info(dev, "Core clock rate: %lu Hz\n", clk_get_rate(clk));
}
这样一开机就能看到时钟是否配对了。

好了,设备树时钟绑定的核心内容就这些。记住:clocks 是接线图,clock-names 是标签,clock-frequency 是快捷方式。三者配合好,驱动就能稳稳地拿到它需要的时钟。