4. 时钟框架入门:Linux Common Clock Framework (CCF)

在骁龙平台 MIPI CSI 调试中,时钟是保证数据链路同步的基础。Linux 内核提供了 Common Clock Framework (CCF) 来统一管理系统中所有的时钟资源。理解 CCF 的 provider/consumer 模型以及设备树中的时钟描述方法,是正确配置 CSI 接口时钟的前提。

4.1 CCF 核心概念:Provider 与 Consumer

CCF 将系统中的时钟设备抽象为两类角色:

  • Clock Provider(时钟提供者):能够产生或转发时钟信号的硬件模块,例如 SoC 内部的 PLL、晶振振荡器、时钟门控单元等。在驱动中,它们注册为 struct clk_hw 并提供操作函数。
  • Clock Consumer(时钟消费者):需要使用时钟信号才能工作的硬件模块,例如 MIPI CSI 控制器、I2C 控制器、GPU 等。它们通过 CCF 的 API 获取并控制时钟频率。
调试要点: 在 MIPI CSI 链路中,Sensor 是时钟消费者,而 SoC 的 CSI PHY 或 Digital Core 通常也是消费者。真正的时钟提供者往往是 SoC 内部的 PLL 或外部晶振。务必在设备树中明确这种供需关系。
CCF 时钟框架:Provider / Consumer 模型 Clock Provider PLL / 晶振 / 门控 struct clk_hw clk_register() 提供时钟信号 Clock Consumer CSI 控制器 / I2C struct clk clk_get() / clk_prepare_enable() 使用时钟信号 clk_get()

4.2 设备树中的时钟描述:clock-cells 与 clocks

设备树通过两个关键属性来描述时钟的拓扑结构:

属性 所属节点 作用 典型值
#clock-cells Clock Provider 定义该时钟控制器输出的时钟索引数量。0 表示单输出,1 表示多输出。 <0> 或 <1>
clocks Clock Consumer 引用该设备所需的时钟源,格式为 <phandle clock-specifier>。 <&gcc GCC_CAMSS_MCLK0_CLK>
clock-names Clock Consumer 为 clocks 属性中的每个时钟指定名称,便于驱动查找。 "camss_mclk"
assigned-clocks 任意节点 在系统启动时强制将某个时钟设置为特定频率或父时钟。 <&gcc GCC_CAMSS_MCLK0_CLK>
关键规则: 一个节点不能同时作为 Provider 和 Consumer 使用同一个 #clock-cells 属性。Provider 节点用 #clock-cells 声明输出端口数量,Consumer 节点用 clocks 引用输入端口。

4.3 典型设备树时钟配置示例

以下是一个骁龙平台 Camera Sensor 节点与 GCC(Global Clock Controller)的时钟绑定示例:

// Clock Provider: GCC 控制器
gcc: clock-controller@100000 {
    compatible = "qcom,gcc-sdm845";
    reg = <0x0100000 0x200000>;
    #clock-cells = <1>;          // 多输出,通过索引区分
    #reset-cells = <1>;
    #power-domain-cells = <1>;
};

// Clock Consumer: Camera Sensor
i2c0: i2c@c170000 {
    status = "okay";
    camera_sensor: camera@10 {
        compatible = "sony,imx577";
        reg = <0x1a>;
        clocks = <&gcc GCC_CAMSS_MCLK0_CLK>;
        clock-names = "camss_mclk";
        assigned-clocks = <&gcc GCC_CAMSS_MCLK0_CLK>;
        assigned-clock-rates = <24000000>;  // 24 MHz
        // ... 其他属性
    };
};

在这个例子中:

  • gcc 节点通过 #clock-cells = <1> 声明它可以输出多个时钟,每个时钟由一个整数索引标识。
  • camera_sensor 节点通过 clocks 属性引用 GCC 输出的 GCC_CAMSS_MCLK0_CLK 时钟。
  • assigned-clock-rates 强制该时钟在启动时初始化为 24 MHz,这是 MIPI CSI 主时钟的常见频率。
设备树时钟绑定关系 GCC (Clock Provider) #clock-cells = <1> 输出: MCLK0, MCLK1, ... phandle: &gcc Camera Sensor (Consumer) clocks = <&gcc ...> clock-names = "camss_mclk" assigned-clock-rates = <24000000> 引用 (phandle + index) 驱动通过 clk_get(dev, "camss_mclk") 获取时钟句柄 然后调用 clk_set_rate() 设置频率

4.4 驱动侧时钟操作流程

在 Camera 驱动中,典型的时钟操作步骤如下:

  1. 获取时钟句柄:struct clk *mclk = devm_clk_get(dev, "camss_mclk");
  2. 准备时钟:clk_prepare(mclk);(可能睡眠)
  3. 设置频率:clk_set_rate(mclk, 24000000);
  4. 使能时钟:clk_enable(mclk);(原子操作)
  5. 使用时钟:MIPI CSI 开始传输数据。
  6. 禁用时钟:clk_disable(mclk);
  7. 停止时钟:clk_unprepare(mclk);
常见错误: 在设备树中忘记添加 assigned-clock-rates 或设置错误频率,导致 Sensor 输出时钟与 SoC 期望不匹配,表现为 MIPI 链路无法锁定或数据错位。调试时务必使用 clk_summary 或 clk_get_rate 确认实际时钟频率。

4.5 调试技巧:验证时钟配置

在系统启动后,可以通过以下方法验证时钟配置是否正确:

方法 命令/操作 预期输出
查看时钟树 cat /sys/kernel/debug/clk/clk_summary 显示所有时钟的 enable_count、prepare_count、rate
检查特定时钟 cat /sys/kernel/debug/clk/gcc_camss_mclk0/clk_rate 24000000
驱动日志 dmesg | grep camera 显示 clk_get 和 clk_set_rate 是否成功
设备树验证 dtc -I fs -O dts /sys/firmware/devicetree/base 反编译设备树,检查 clocks 属性是否完整