当前位置: 首页 > news >正文

驱动开发硬核特训 · Day 25 (附加篇):从设备树到驱动——深入理解Linux时钟子系统的实战链路


一、前言

在嵌入式Linux开发中,无论是CPU、外设控制器,还是简单的GPIO扩展器,大多数硬件模块都离不开时钟信号的支撑。

时钟子系统(Clock Subsystem),作为Linux内核中基础设施的一部分,为设备提供统一、灵活、可控的时钟管理机制。
然而,时钟子系统的工作方式常常隐藏在设备树配置与驱动框架之下,让初学者难以直观感知。

今天,我们将通过一个完整、清晰、实战化的例子,带你从设备树定义,到内核驱动调用核心API全面理解时钟子系统的核心知识点和实际用途


在这里插入图片描述

二、实战场景介绍

本次实战选取的例子是 —— UART 控制器(串口)

在 i.MX8MP 平台上,UART1 控制器是一个非常典型的时钟消费者。它需要一个稳定的时钟源来驱动波特率生成器,确保数据收发的准确性。


三、设备树中的时钟配置

在设备树中,为每个时钟消费者(比如 UART)指定它需要的时钟源。来看具体的设备树片段(源自 i.MX8MP EVK 板):

&uart1 { /* BT UART */pinctrl-names = "default";pinctrl-0 = <&pinctrl_uart1>;assigned-clocks = <&clk IMX8MP_CLK_UART1>;assigned-clock-parents = <&clk IMX8MP_SYS_PLL1_80M>;uart-has-rtscts;status = "okay";
};

解释

  • assigned-clocks:指定要配置的时钟节点(这里是UART1的根时钟)。
  • assigned-clock-parents:指定该时钟的上游父时钟(这里是系统PLL1输出的80MHz频率)。
  • status = "okay":使能设备。

小结

在设备树中,UART1通过 assigned-clocks 指定了自己依赖的时钟源。
这一步是让内核启动时,能够正确配置 UART 所需时钟。


四、时钟子系统在内核中的处理流程

内核解析设备树时,会执行以下步骤:

  1. 解析 assigned-clocks 属性

    • 调用 of_clk_get() 获取时钟phandle。
  2. 设置父时钟(if assigned-clock-parents)

    • 调用 clk_set_parent()
  3. 设置频率(if assigned-clock-rates)

    • 调用 clk_set_rate()
  4. 将时钟与设备绑定

    • 供后续驱动使用 clk_get() 提取并控制时钟。

总结一句话:内核在启动时,根据设备树,把设备需要的时钟准备好,驱动层可以直接使用。


五、驱动中的核心函数调用链

进入驱动注册时,通常在 probe() 函数中,设备驱动需要主动申请并启用时钟。

来看典型的 Linux 串口驱动核心代码(以drivers/tty/serial/fsl_lpuart.c为例):

static int lpuart_probe(struct platform_device *pdev)
{struct resource *res;struct clk *clk;struct lpuart_port *sport;// ... 省略其他初始化代码clk = devm_clk_get(&pdev->dev, NULL);if (IS_ERR(clk))return PTR_ERR(clk);clk_prepare_enable(clk);// 后续设置UART寄存器,开始工作return 0;
}

解释

  • devm_clk_get(&pdev->dev, NULL):从时钟子系统中获取设备绑定的时钟(根据设备树内容解析)。
  • clk_prepare_enable(clk):准备并使能时钟,确保模块时钟打开,才能访问寄存器或工作。

这两个函数是驱动中最核心的标准时钟处理流程


六、核心知识点总结

过程内容说明
设备树定义通过 assigned-clocks assigned-clock-parents指定设备需要使用的时钟及父时钟关系
内核解析设备树使用 of_clk_get()clk_set_parent() 等接口完成设备初步时钟配置
驱动中申请时钟使用 devm_clk_get() 获取时钟句柄与设备生命周期绑定,自动管理释放
启用时钟使用 clk_prepare_enable()确保时钟开启,模块能够正常读写工作
关闭时钟使用 clk_disable_unprepare()(通常在 remove 时)释放功耗资源,避免悬空开启

七、完整链路小结(可视化)

[设备树]|v
解析 assigned-clocks → 绑定 clk_hw → 初始化频率和父时钟|v
驱动 probe 阶段|v
devm_clk_get() 获取时钟|v
clk_prepare_enable() 打开时钟|v
模块正常运行

八、实战小问答(加深记忆)

  • Q:如果不调用 clk_prepare_enable() 会怎么样?
    ➔ A:设备可能无法正常访问寄存器(时钟门控),导致挂死或者硬件超时错误。

  • Q:为什么用 devm_clk_get()
    ➔ A:简化驱动资源管理,避免忘记释放时钟资源导致内存泄漏。

  • Q:时钟源一定是固定的吗?
    ➔ A:不一定,有些时钟(如CPU频率)是动态可变的,需要动态控制频率。

九、driver/clk/子系统的核心组成

在 Linux 内核中,driver/clk/ 目录下是整个 Common Clock Framework(CCF) 的具体实现。

它包含两大部分:

部分作用
核心框架代码(Generic)提供统一时钟注册、获取、启用、频率调整等API
平台时钟驱动(Platform-specific)各芯片平台的具体时钟树实现,比如 imx8mp

十、实战中最关键的核心文件和功能

我们只讲跟设备树解析、驱动使用真正相关的代码,不浪费篇幅。

目录/文件作用备注
drivers/clk/clk.c核心通用时钟框架逻辑所有 clk_register, clk_get, clk_prepare_enable 都在这里最终处理
drivers/clk/clk-fixed-rate.c实现固定频率的时钟(如设备树里的 fixed-clock)常用于 camera、USB、PCIE refclk
drivers/clk/imx/clk-imx8mp.ci.MX8MP 平台特有的时钟树搭建把 IMX8MP 平台的所有时钟源、分频器、复用器建立起来

十一、真正重要的核心函数讲解(来自 driver/clk/clk.c)

11.1 clk_register()

🔵 作用:
在内核启动时,或者平台驱动初始化时,把一个新的时钟节点注册到内核的时钟树中。

🔵 主要流程:

  • struct clk_hw 转换为 struct clk_core
  • clk_core 挂到内核全局时钟链表
  • 处理时钟父子关系

🔵 关键源码位置:

struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{struct clk_core *core;...core = kzalloc(sizeof(*core), GFP_KERNEL);core->hw = hw;...hlist_add_head(&core->node, &clk_root_list);return __clk_create_clk(core, NULL);
}

这里的 clk_core 是真正管理时钟状态的内部结构。


11.2 of_clk_get()devm_clk_get()

🔵 作用:

  • of_clk_get():从设备树 phandle 提取时钟节点。
  • devm_clk_get():在驱动中调用,帮你自动结合生命周期管理。

🔵 主要流程:

  • 根据设备树 phandle 查找 clk_provider
  • 通过 provider的 of_clk_src_onecell_get() 或类似接口拿到 clk

🔵 关键源码位置:

struct clk *of_clk_get(struct device_node *np, int index)
{...provider = of_parse_phandle(np, "clocks", index);...return provider->get(provider->data, index);
}

最终就是在驱动层 devm_clk_get() 里面,调用 of_clk_get(),并返回一个 clk 结构体给设备驱动用。


11.3 clk_prepare_enable()clk_disable_unprepare()

🔵 作用:

  • clk_prepare_enable():打开时钟,并确保硬件模块有时钟供给。
  • clk_disable_unprepare():关闭时钟,释放资源。

🔵 核心调用逻辑:

int clk_prepare_enable(struct clk *clk)
{int ret;ret = clk_prepare(clk);if (ret)return ret;ret = clk_enable(clk);if (ret)clk_unprepare(clk);return ret;
}

本质就是调用底层时钟驱动注册时定义好的 enable/disable 函数指针。

比如:

  • PLL的 enable
  • gate clock 的开关
  • mux clock 的切换

十二、fixed-clock 举例(drivers/clk/clk-fixed-rate.c)

在设备树中,如果定义了:

pcie0_refclk: pcie0-refclk {compatible = "fixed-clock";#clock-cells = <0>;clock-frequency = <100000000>;
};

内核会自动匹配到 drivers/clk/clk-fixed-rate.c 中的 of_fixed_clk_setup()

static void __init of_fixed_clk_setup(struct device_node *node)
{struct clk_fixed_rate *fixed;...fixed->hw.init = &init;clk_register(NULL, &fixed->hw);
}

➔ 最后也是通过 clk_register() 注册到内核时钟树。

所以:

  • fixed-clock → 固定频率的时钟节点
  • 设备(比如 PCIe PHY)可以直接引用它做为参考时钟

✨ 最重要总结(一句话)

✅ 无论是外设驱动,还是内核框架,
✅ 设备树 → of_clk_get → clk_register → clk_prepare_enable
✅ 整个链路最终就是围绕 clk_coreclk_ops 把时钟管理起来,
✅ 保证所有设备能统一管理时钟、动态调频、动态开关。


📋 最后再给你一张核心流程小图(助记忆)

[设备树 clocks属性]↓
[of_clk_get()]↓
[clk_register() 把hw挂到core]↓
[devm_clk_get()]↓
[clk_prepare_enable()]↓
[设备驱动正常工作]

相关文章:

  • 高德地图线上截图瓦片地图加载不完全
  • 4月29日星期二今日早报简报微语报早读
  • dify升级最新版本(保留已创建内容)
  • 黑马Redis(四)
  • 基于非递归求解的汉诺塔超级计算机堆栈与数据区设计方案
  • 13.继承、重载、重写、多态、抽象类、接口、final、Static的学习
  • Kubernetes Label 和 Selector新手入门学习
  • 【Axure高保真原型】动态地图路线
  • 考研408-计算机组成原理冲刺考点(4-5章)
  • SpringSecurity+JWT
  • C语言 | C语言入门基础之指针详解,编程技巧、规则、注意事项、易出问题、问题如何解决
  • Windows 桌面个性高效组件工具
  • Java—— 四道算法经典题
  • AI与软件测试的未来:如何利用智能自动化改变测试流程
  • 设计模式(工厂模式)
  • VUE篇之树形特殊篇
  • 探寻软件稳定性的奥秘
  • 【最新 MCP 战神手册 09】利用资源和提示增强上下文
  • 【记录】Python调用大模型(以Deepseek和Qwen为例)
  • 使用 np.zeros_like(label) 保存预测概率时发现数据类型不匹配导致的隐式类型转换
  • 招商蛇口:一季度营收约204亿元,净利润约4.45亿元
  • 三大白电巨头去年净利近900亿元:美的持续领跑,格力营收下滑
  • 打造全域消费场景,上海大世界百个演艺娱乐新物种待孵化
  • 在循环往复的拍摄中,重新发现世界
  • “五一”假期倒计时,节前错峰出游机票降价四成
  • 江西省国资委原副主任李键主动向组织交代问题,接受审查调查