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 个时钟。
#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。它通常用在两种场景:
- 固定频率的晶振:比如 24MHz 的晶振,不需要复杂的 PLL 配置。
- 驱动不想走 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 知识结构图:时钟绑定的完整流程
下面这张图展示了从设备树到驱动获取时钟的完整链路:
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 是快捷方式。三者配合好,驱动就能稳稳地拿到它需要的时钟。