4. 时钟子系统:时钟框架、clk provider、clk consumer、设备树时钟绑定

时钟子系统是嵌入式 Linux 驱动开发的基石。对于 V4L2 设备而言,正确的时钟配置直接决定了摄像头传感器、MIPI 接口、ISP 等模块能否在正确的频率下工作。本章将深入剖析 Linux 通用时钟框架(CCF)的设计理念、核心数据结构、Provider/Consumer 模型以及设备树中的时钟绑定规范。

4.1 通用时钟框架(CCF)概述

Linux 通用时钟框架(Common Clock Framework, CCF)旨在统一 SoC 上种类繁多的时钟硬件,为驱动开发者提供一套标准化的 API。CCF 的核心思想是将时钟硬件抽象为树状结构,通过 Provider(提供者)和 Consumer(使用者)模型实现解耦。

核心设计原则: 时钟树以“根时钟”为起点,经过多级 PLL、分频器、多路选择器(MUX)和门控(Gate),最终到达外设。驱动只需通过 clk_get() 获取句柄,调用 clk_prepare_enable() 等 API 即可操作时钟,无需关心底层寄存器细节。
通用时钟框架(CCF)软件架构图 用户空间 (User Space) 内核空间 (Kernel Space) Consumer API clk_get / clk_prepare_enable CCF Core clk_register / clk_ops Provider API clk_register_fixed_rate 硬件抽象层 (Hardware Abstraction Layer) struct clk_hw / struct clk_ops (recalc_rate / set_rate / enable / disable) 硬件时钟源 (PLL / OSC / Divider / Gate) 说明:Consumer 通过标准 API 调用 CCF Core,CCF Core 回调 Provider 注册的 clk_ops 操作硬件。

4.2 核心数据结构与 API

CCF 框架围绕几个关键数据结构构建,理解它们是编写 Provider 和 Consumer 驱动的前提。

数据结构 所属角色 作用描述
struct clk_hw Provider 硬件时钟的抽象,包含指向 struct clk_ops 的指针和私有数据。
struct clk_ops Provider 操作函数集,包括 prepare、unprepare、enable、disable、recalc_rate、round_rate、set_rate 等。
struct clk Consumer Consumer 看到的时钟句柄,通过 clk_get() 获得,用于调用所有 Consumer API。
struct clk_init_data Provider 注册时钟时提供的初始化数据,包含时钟名称、操作函数集、父时钟列表等。
关键区别: struct clk_hw 是 Provider 视角的硬件表示,而 struct clk 是 Consumer 视角的软件句柄。驱动开发中,Provider 驱动主要操作 clk_hw,Consumer 驱动只操作 clk。

4.3 clk provider:时钟提供者驱动编写

Provider 驱动负责向 CCF 注册一个或多个时钟。典型的注册流程如下:

  1. 分配并初始化 struct clk_hw 结构体。
  2. 填充 struct clk_init_data,设置时钟名称、父时钟、标志位和操作函数集。
  3. 调用注册函数,如 clk_hw_register()、devm_clk_hw_register() 或专用注册器(如 clk_register_fixed_rate())。
  4. 实现 clk_ops 中的回调函数,操作底层硬件寄存器。

以下是一个简单的固定频率时钟 Provider 示例:

#include <linux/clk-provider.h>
#include <linux/platform_device.h>

static struct clk_hw *my_fixed_clk_hw;

static int my_clk_probe(struct platform_device *pdev)
{
    struct clk_init_data init = { };
    int ret;

    init.name = "my_fixed_clk";
    init.ops = &clk_fixed_rate_ops;  // 使用通用固定频率操作函数
    init.flags = CLK_IS_CRITICAL;    // 标记为关键时钟,防止被关闭

    my_fixed_clk_hw = devm_kzalloc(&pdev->dev, sizeof(*my_fixed_clk_hw), GFP_KERNEL);
    if (!my_fixed_clk_hw)
        return -ENOMEM;

    my_fixed_clk_hw->init = &init;

    ret = devm_clk_hw_register(&pdev->dev, my_fixed_clk_hw);
    if (ret)
        return ret;

    return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get, my_fixed_clk_hw);
}
注意事项: Provider 驱动必须调用 devm_of_clk_add_hw_provider() 将时钟暴露给设备树,否则 Consumer 无法通过 clk_get() 找到该时钟。对于可变频率时钟(如 PLL),必须实现 recalc_rate、round_rate 和 set_rate 回调。

4.4 clk consumer:时钟使用者驱动编写

Consumer 驱动(如 V4L2 摄像头驱动)通过标准 API 获取和操作时钟。典型的使用模式如下:

#include <linux/clk.h>

struct device *dev = &pdev->dev;
struct clk *sensor_clk;
int ret;

// 1. 获取时钟句柄(时钟名称需与设备树中的 clock-names 匹配)
sensor_clk = devm_clk_get(dev, "xvclk");
if (IS_ERR(sensor_clk)) {
    dev_err(dev, "Failed to get xvclk\n");
    return PTR_ERR(sensor_clk);
}

// 2. 准备并启用时钟(prepare 可能睡眠,enable 是原子操作)
ret = clk_prepare_enable(sensor_clk);
if (ret) {
    dev_err(dev, "Failed to enable xvclk\n");
    return ret;
}

// 3. 设置时钟频率(可选,通常由设备树指定)
ret = clk_set_rate(sensor_clk, 24000000); // 设置为 24MHz
if (ret)
    dev_warn(dev, "Failed to set xvclk rate\n");

// 4. 获取实际频率(用于配置传感器 PLL)
unsigned long rate = clk_get_rate(sensor_clk);
dev_info(dev, "xvclk rate: %lu Hz\n", rate);

// ... 进行 V4L2 设备初始化 ...

// 5. 关闭时钟(在驱动移除或休眠时调用)
clk_disable_unprepare(sensor_clk);
API 函数 功能 是否可睡眠
clk_get(dev, id) 获取时钟句柄 是
clk_prepare(clk) 准备时钟(可能涉及 PLL 锁定等耗时操作) 是
clk_enable(clk) 使能时钟输出(原子操作) 否
clk_prepare_enable(clk) 组合操作:prepare + enable 是
clk_set_rate(clk, rate) 设置时钟频率 是
clk_get_rate(clk) 获取当前时钟频率 否
clk_disable_unprepare(clk) 组合操作:disable + unprepare 是

4.5 设备树时钟绑定

设备树是连接 Provider 和 Consumer 的桥梁。时钟绑定遵循 clocks 和 clock-names 属性规范。

4.5.1 Provider 节点绑定

时钟 Provider 节点需要定义 #clock-cells 属性,指定输出时钟的数量。对于单输出时钟,#clock-cells = <0>;对于多输出时钟(如 PLL 输出多个分频时钟),#clock-cells = <1>,Consumer 通过索引指定。

/* 固定频率时钟 Provider */
clk_24m: clk-24m {
    compatible = "fixed-clock";
    #clock-cells = <0>;
    clock-frequency = <24000000>;
    clock-output-names = "xvclk";
};

/* SoC 内部时钟控制器 Provider */
clkc: clock-controller@ff760000 {
    compatible = "rockchip,rk3399-cru";
    reg = <0x0 0xff760000 0x0 0x1000>;
    #clock-cells = <1>;  /* 多输出时钟,通过索引引用 */
    #reset-cells = <1>;
};

4.5.2 Consumer 节点绑定

Consumer 节点通过 clocks 属性引用 Provider,并通过 clock-names 为每个时钟指定名称,便于驱动通过 devm_clk_get(dev, name) 获取。

/* V4L2 摄像头传感器节点 */
i2c@ff3d0000 {
    #address-cells = <1>;
    #size-cells = <0>;

    camera_sensor: camera@10 {
        compatible = "sony,imx219";
        reg = <0x10>;
        clocks = <&clk_24m>;           /* 引用 Provider */
        clock-names = "xvclk";          /* 驱动中使用的名称 */
        clock-frequency = <24000000>;   /* 期望频率(非标准属性,但常用) */
        /* ... 其他属性 ... */
    };
};
设备树时钟绑定关系图 clk_24m (Provider) compatible = "fixed-clock" #clock-cells = <0> clock-frequency = <24000000> camera_sensor (Consumer) compatible = "sony,imx219" clocks = <&clk_24m> clock-names = "xvclk" clocks 属性引用 Provider 驱动 clk_register / of_clk_add_provider Consumer 驱动 devm_clk_get(dev, "xvclk") 说明:设备树中的 clocks 属性建立 Provider 与 Consumer 的静态连接;驱动运行时通过 clock-names 获取句柄。 #clock-cells = <0> 表示 Provider 只有一个输出时钟,Consumer 引用时无需指定索引。

4.6 V4L2 驱动中的时钟实战要点

在 V4L2 摄像头驱动中,时钟管理直接影响图像采集的稳定性。以下是几个关键实战要点:

  • 时钟顺序: 必须先使能时钟,再初始化传感器 I2C 寄存器;关闭时钟必须在传感器进入休眠之后。
  • 频率精度: 传感器通常需要精确的时钟频率(如 24MHz ± 1%)。使用 clk_round_rate() 检查硬件支持的最佳频率,避免因频率偏差导致图像抖动。
  • 异步时钟域: 如果传感器使用外部晶振,而 MIPI CSI 接口使用 SoC 内部 PLL,两者属于不同时钟域。驱动中需确保数据同步,必要时启用硬件握手信号。
  • 电源与时钟联动: 某些 SoC 要求先上电再提供时钟。建议在驱动中按“电源 -> 时钟 -> 复位释放”的顺序初始化。
调试技巧: 使用 /sys/kernel/debug/clk/clk_summary 查看时钟树状态,确认时钟是否已启用、频率是否正确。这是排查 V4L2 设备无图像输出问题的第一步。
# 查看时钟树摘要
cat /sys/kernel/debug/clk/clk_summary

# 输出示例(部分)
#   clock                         enable_cnt  prepare_cnt  rate        accuracy   phase
# ---------------------------------------------------------------------------------------
# xvclk                                   1            1    24000000          0       0
# pll_sensor                              1            1    594000000         0       0
# csi_mclk                                1            1    24000000          0       0