3、时钟绑定核心概念:clocks、clock-names、clock-cells、assigned-clocks等属性详解
设备树中时钟绑定的核心在于通过一组标准属性,将消费设备(Consumer)与时钟提供者(Provider)之间的依赖关系、时钟配置参数以及初始化行为描述清楚。本章逐一剖析 clocks、clock-names、#clock-cells、assigned-clocks、assigned-clock-parents 和 assigned-clock-rates 这六个关键属性,并给出它们在驱动中的对应关系。
3.1 属性全景速览
下表列出时钟绑定中最常用的六个属性及其作用域与核心语义:
| 属性名 | 所属节点 | 核心语义 | 是否必需 |
|---|---|---|---|
#clock-cells |
时钟提供者 | 声明输出时钟的索引维度(0 或 1) | 是(提供者) |
clocks |
时钟消费者 | 列出该设备依赖的时钟句柄列表 | 是(消费者) |
clock-names |
时钟消费者 | 为 clocks 列表中的每个句柄命名 |
推荐 |
assigned-clocks |
时钟消费者 | 指定需要在驱动 probe 前完成配置的时钟 | 按需 |
assigned-clock-parents |
时钟消费者 | 为 assigned-clocks 指定父时钟 |
按需 |
assigned-clock-rates |
时钟消费者 | 为 assigned-clocks 指定目标频率 |
按需 |
#clock-cells 声明输出能力,消费者节点通过 clocks 引用提供者,clock-names 提供可读别名,assigned-* 系列属性用于早期时钟拓扑配置。
3.2 #clock-cells:提供者的输出维度
#clock-cells 定义在时钟提供者节点中,取值只能是 0 或 1:
- 0:提供者只输出一个时钟,消费者引用时不需要附加索引参数。
- 1:提供者输出多个时钟,消费者引用时必须附加一个 32 位整数作为索引。
典型示例:
/* 固定频率时钟,只输出一个时钟 */
osc24M: oscillator {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
};
/* 多功能时钟控制器,输出多个时钟 */
clkgen: clock-controller@ff760000 {
compatible = "vendor,clkgen";
reg = <0xff760000 0x1000>;
#clock-cells = <1>;
};
消费者引用时,#clock-cells = <0> 的提供者只需一个 phandle;#clock-cells = <1> 的提供者需要 phandle + 索引:
uart0: serial@ff000000 {
clocks = <&osc24M>, <&clkgen 3>;
clock-names = "baud", "bus";
};
3.3 clocks 与 clock-names:消费者的依赖声明
clocks 属性是一个 phandle 列表,每个元素指向一个时钟提供者,必要时附带索引参数。列表顺序即驱动中获取时钟时的默认顺序。
clock-names 为每个时钟句柄赋予一个字符串别名,驱动通过 devm_clk_get(dev, name) 按名获取,避免依赖硬编码索引。
clock-names。这使设备树更具可读性,且当未来增加新时钟时无需修改驱动索引逻辑。
驱动侧获取示例:
/* 设备树片段 */
i2c1: i2c@ff3f0000 {
clocks = <&clkgen 7>, <&osc24M>;
clock-names = "pclk", "sclk";
};
/* 驱动代码 */
struct clk *pclk, *sclk;
pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(pclk))
return PTR_ERR(pclk);
sclk = devm_clk_get(dev, "sclk");
if (IS_ERR(sclk))
return PTR_ERR(sclk);
3.4 assigned-clocks 系列:早期时钟拓扑配置
assigned-clocks、assigned-clock-parents 和 assigned-clock-rates 用于在设备驱动 probe 之前,由内核时钟框架完成时钟的父选择与频率设置。这三个属性必须配合使用:
assigned-clocks:列出需要配置的时钟句柄列表。assigned-clock-parents:为每个assigned-clocks指定父时钟句柄(可选)。assigned-clock-rates:为每个assigned-clocks指定目标频率(可选)。
典型用法:
&uart0 {
assigned-clocks = <&clkgen 3>, <&clkgen 4>;
assigned-clock-parents = <&osc24M>, <&clkgen 1>;
assigned-clock-rates = <48000000>, <100000000>;
};
上述配置的含义:
- 将
clkgen输出 3 的父时钟切换为osc24M。 - 将
clkgen输出 4 的父时钟切换为clkgen输出 1。 - 将
clkgen输出 3 的频率设置为 48 MHz。 - 将
clkgen输出 4 的频率设置为 100 MHz。
assigned-clock-parents 和 assigned-clock-rates 列表长度必须与 assigned-clocks 一致。如果某个时钟只配父不配频率,对应位置用空参数 <0> 占位;反之亦然。内核在 of_clk_set_defaults() 中按顺序逐项处理。
3.5 属性关系与数据流
下图展示从设备树属性到内核时钟框架核心数据结构的映射关系:
3.6 驱动调试中的常见陷阱
基于上述属性,在调试时钟绑定问题时,以下三类错误最为常见:
| 错误类型 | 典型现象 | 根因分析 | 排查方法 |
|---|---|---|---|
| #clock-cells 不匹配 | clk_get 返回 -EPROBE_DEFER 或 -EINVAL | 消费者引用时多传或少传了索引参数 | 对比提供者节点的 #clock-cells 与消费者 clocks 中的参数个数 |
| clock-names 拼写错误 | devm_clk_get(dev, "name") 返回 -ENOENT | 设备树中名称与驱动代码字符串不一致 | 使用 cat /sys/kernel/debug/clk/clk_summary 查看实际注册的时钟名 |
| assigned-* 列表长度不一致 | 部分时钟频率或父时钟未生效 | assigned-clock-parents 或 assigned-clock-rates 元素个数少于 assigned-clocks | 检查 DTS 中三个列表的长度是否严格相等 |
dev_dbg(dev, "clk %s rate: %lu\n", name, clk_get_rate(clk)); 输出,配合 clk_summary 调试文件,可以快速验证 assigned-* 配置是否按预期生效。
3.7 完整设备树示例
以下是一个集成了所有核心属性的完整设备树片段,展示 UART 控制器如何依赖两个时钟并完成早期配置:
/* 时钟提供者 */
osc24M: oscillator {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
};
clkgen: clock-controller@ff760000 {
compatible = "vendor,clkgen";
reg = <0xff760000 0x1000>;
#clock-cells = <1>;
clocks = <&osc24M>;
clock-names = "osc";
};
/* 时钟消费者 */
uart0: serial@ff000000 {
compatible = "vendor,uart";
reg = <0xff000000 0x100>;
interrupts = <0 32 4>;
/* 依赖两个时钟:索引 3 为波特率时钟,索引 7 为总线时钟 */
clocks = <&clkgen 3>, <&clkgen 7>;
clock-names = "baud", "bus";
/* 早期配置:波特率时钟父时钟选 osc24M,频率设为 48 MHz */
assigned-clocks = <&clkgen 3>;
assigned-clock-parents = <&osc24M>;
assigned-clock-rates = <48000000>;
};
驱动侧获取时钟的代码片段:
static int uart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct clk *baud_clk, *bus_clk;
baud_clk = devm_clk_get(dev, "baud");
if (IS_ERR(baud_clk))
return dev_err_probe(dev, PTR_ERR(baud_clk), "failed to get baud clk");
bus_clk = devm_clk_get(dev, "bus");
if (IS_ERR(bus_clk))
return dev_err_probe(dev, PTR_ERR(bus_clk), "failed to get bus clk");
dev_info(dev, "baud rate: %lu, bus rate: %lu\n",
clk_get_rate(baud_clk), clk_get_rate(bus_clk));
return 0;
}
通过本章的学习,你应该能够准确理解设备树中时钟绑定的六个核心属性,并能在实际项目中正确编写和调试时钟绑定描述。