驱动开发硬核特训 · Day 27(下篇):深入掌握 Common Clock Framework 架构与实战开发

节。


在本篇内容中,我们将围绕 Linux 内核中的时钟子系统核心架构 ------ Common Clock Framework(简称 CCF)展开深入讲解,目标是帮助你全面理解其设计理念、主要数据结构、注册流程、驱动实现方式,以及如何基于 NXP i.MX8MP 平台进行完整的时钟驱动开发。


一、Common Clock Framework 简介

Common Clock Framework(CCF)是 Linux 内核自 v3.4 起引入的通用时钟架构,用于统一管理 SoC 上所有的时钟资源,解决平台异构、驱动分裂、代码冗余等问题。

CCF 的设计目标是:

  • 提供统一的时钟抽象接口(如 clk_get() / clk_prepare() / clk_enable())。
  • 支持复杂的时钟树结构,包括分频、门控、复用器等组件。
  • 支持时钟源的动态切换与频率动态调整(DFS)。
  • 解耦设备驱动与时钟控制的实现。

二、CCF 的三大核心角色与架构总览

在上一篇中我们已概述三个核心角色:

角色 作用 示例
时钟提供者(Provider) 注册并实现时钟控制逻辑,如 PLL、分频器、门控器 NXP CCM 时钟控制器驱动
时钟消费者(Consumer) 使用时钟接口获取和启用时钟 UART/I2C 等外设驱动
时钟框架(Framework) 统一管理所有时钟,维护拓扑关系与接口 drivers/clk/clk.c

架构示意图:

text 复制代码
+---------------------+      +-------------------+
| UART 驱动 (Consumer)|<---->|   CCF 框架层       |
+---------------------+      +-------------------+
                                      ↑
                                      |
                            +---------------------+
                            | 时钟控制器驱动       |
                            | (如 fsl-imx8mp-ccm)  |
                            +---------------------+

三、CCF 核心数据结构分析

1. struct clk_hw

该结构体表示一个底层时钟硬件实体,由时钟提供者实现:

c 复制代码
struct clk_hw {
    struct clk_core *core;
    struct clk_init_data *init;
    // 可扩展的私有数据指针
};

2. struct clk_ops

用于定义操作时钟的函数集,是最关键的回调接口:

c 复制代码
struct clk_ops {
    int  (*enable)(struct clk_hw *hw);
    void (*disable)(struct clk_hw *hw);
    unsigned long (*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate);
    long (*round_rate)(struct clk_hw *hw, unsigned long rate,
                       unsigned long *parent_rate);
    int  (*set_rate)(struct clk_hw *hw, unsigned long rate,
                     unsigned long parent_rate);
    ...
};

3. struct clk_init_data

用于注册时钟时的初始化信息:

c 复制代码
struct clk_init_data {
    const char *name;
    const struct clk_ops *ops;
    const char * const *parent_names;
    u8 num_parents;
    unsigned long flags;
};

四、时钟提供者(Provider)注册流程

Provider 通过 clk_register()devm_clk_hw_register()clk_hw 实例注册给框架,框架内部建立 clk_core 对象并添加至全局时钟树。

示例流程:

  1. 定义 clk_ops 实现;
  2. 构造 clk_hw
  3. 填写 clk_init_data
  4. 调用 devm_clk_hw_register() 完成注册;
  5. 若是 platform 设备,通过 of_clk_add_hw_provider() 提供给设备树接口。

五、实战开发:NXP i.MX8MP 时钟驱动解析

我们以 NXP i.MX8MP 的 UART2 根时钟 IMX8MP_CLK_UART2_ROOT 为例,讲解从时钟控制器提供者,到 UART2 驱动消费者的完整链路。

1. 设备树配置分析

dts 复制代码
uart2: serial@30890000 {
    compatible = "fsl,imx8mp-uart", "fsl,imx6q-uart";
    reg = <0x30890000 0x10000>;
    interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clk IMX8MP_CLK_UART2_ROOT>,
             <&clk IMX8MP_CLK_UART2_ROOT>;
    clock-names = "ipg", "per";
    status = "disabled";
};

<&clk IMX8MP_CLK_UART2_ROOT> 表示设备节点引用了 ID 为 IMX8MP_CLK_UART2_ROOT 的时钟,provider 必须实现它。

2. Provider 实现(drivers/clk/imx/clk-imx8mp.c)

c 复制代码
static const struct imx8m_clk_root_clk uart2_root = {
    .id = IMX8MP_CLK_UART2_ROOT,
    .name = "uart2_root_clk",
    .parent_names = (const char *[]){ "uart2_div" },
    .num_parents = 1,
    .flags = CLK_SET_RATE_PARENT,
};

注册流程:

c 复制代码
hw = imx8m_clk_hw_register_composite(NULL, clk_root->name,
                                     clk_root->parent_names, clk_root->num_parents,
                                     mux_hw, &clk_mux_ops,
                                     rate_hw, &clk_divider_ops,
                                     gate_hw, &clk_gate_ops,
                                     clk_root->flags);

注: 每个 root clock 通常由一个复用器、一个分频器和一个门控组成,符合 CCF 的复合时钟模型。


六、时钟消费者如何使用时钟

驱动中使用以下接口获取与控制时钟:

c 复制代码
struct clk *clk = devm_clk_get(dev, "ipg");
clk_prepare_enable(clk);
...
clk_disable_unprepare(clk);

内核根据 clock-names 字符串与设备树中的 <&clk ID> 找到实际的 clk_core 实例,进一步调用对应 clk_ops 接口。


七、完整代码实例:模拟一个外设时钟 Provider

以下模拟一个名为 "demo-gate-clk" 的时钟,基于 GPIO 控制实现一个简单门控时钟:

1. 示例平台驱动结构:

c 复制代码
static struct clk_hw *demo_clk_hw;
static struct clk_ops demo_clk_ops = {
    .enable = demo_clk_enable,
    .disable = demo_clk_disable,
};

static int demo_clk_probe(struct platform_device *pdev)
{
    struct clk_init_data init;
    demo_clk_hw = devm_kzalloc(&pdev->dev, sizeof(*demo_clk_hw), GFP_KERNEL);

    init.name = "demo-gate-clk";
    init.ops = &demo_clk_ops;
    init.num_parents = 0;
    init.flags = 0;
    demo_clk_hw->init = &init;

    clk_hw_register(&pdev->dev, demo_clk_hw);
    of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_simple_get, demo_clk_hw);
    return 0;
}

2. 在设备树中描述该时钟:

dts 复制代码
demo_clk: demo-clk {
    compatible = "vendor,demo-gate-clk";
    #clock-cells = <0>;
};

3. 在其他设备中消费该时钟:

dts 复制代码
my_peripheral@12340000 {
    ...
    clocks = <&demo_clk>;
    clock-names = "core";
};

八、Framework 的统一管理机制

Linux 内核 CCF 框架通过 clk_core 全局对象池,维持整个时钟拓扑结构,并提供统一接口进行操作与调试。

核心文件位于 drivers/clk/clk.c,负责:

  • 初始化时钟树结构;
  • 实现 clk_get()clk_set_rate() 等函数;
  • 管理依赖关系(父子时钟);
  • 调度跨时钟域变更时的同步机制。

框架的调试接口如:

bash 复制代码
cat /sys/kernel/debug/clk/clk_summary

可查看当前系统所有时钟状态。


九、总结与实战建议

  • Provider 注册逻辑必须按 CCF 规则实现 clk_hwclk_ops,并注册给框架;
  • Consumer 驱动应只依赖标准 clk_*() 接口,不直接操作硬件;
  • Framework 负责维护拓扑、管理父子关系、支持动态调频调占;
  • 实战中,先梳理平台提供的 clock ID 定义(如 imx8mp-clock.h),再定位 clock controller 驱动;
  • 开发自定义外设或 FPGA 时钟控制时,也可编写 Provider 兼容设备树接口。

十、每日提问与答案

问题 1:一个时钟可以有多个父时钟吗?如何选择?

回答:

可以。CCF 支持多父时钟的复用器机制,注册 clk_hw 时传入多个 parent_names,并实现 set_parent()get_parent() 回调。使用者可调用 clk_set_parent() 选择目标父时钟。


问题 2:如何调试某个时钟未启用的问题?

回答:

可通过以下方式定位:

  • cat /sys/kernel/debug/clk/clk_summary 查看时钟是否启用;
  • 查看是否被 Consumer 使用并调用 clk_prepare_enable()
  • 检查 Provider 的 clk_ops.enable() 是否被成功执行;
  • 验证时钟的 parent 是否启用,是否存在 gating 问题。

以上即为 Day 27 下篇全部内容,完整讲解了 CCF 架构设计与实际使用方式。

下一日我们将正式进入子系统与电源管理集成的驱动开发专题。

视频教程请关注 B 站:"嵌入式 Jerry"

相关推荐
小小工匠3 小时前
架构思维:利用全量缓存架构构建毫秒级的读服务
架构·binlog·全量缓存架构
天堂的恶魔9465 小时前
Docker —— 技术架构的演进
docker·容器·架构
蚂蚁数据AntData5 小时前
DB-GPT V0.7.1 版本更新:支持多模态模型、支持 Qwen3 系列,GLM4 系列模型 、支持Oracle数据库等
大数据·数据库·gpt·oracle·架构
蒂法就是我6 小时前
Kafka 的服务端的物理存储架构是什么?零拷贝,mmap,sendfile、DMA gather又是什么?
分布式·架构·kafka
__Benco6 小时前
OpenHarmony平台驱动开发(一),ADC
人工智能·驱动开发·harmonyos
大G哥15 小时前
【微服务】SpringBoot制作Docker镜像接入SkyWalking详解
spring boot·docker·微服务·架构·skywalking
岁月漫长_16 小时前
【项目归档】数据抓取+GenAI+数据分析
分布式·chatgpt·架构·flask·fastapi·celery
乌旭16 小时前
AIGC算力消耗白皮书:Stable Diffusion vs Midjourney的架构成本差异
人工智能·深度学习·stable diffusion·架构·aigc·midjourney·gpu算力
夏旭泽21 小时前
系统架构-架构评估
架构·系统架构