節。
在本篇內容中,我們將圍繞 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 |
架構示意圖:
+---------------------+ +-------------------+
| UART 驅動 (Consumer)|<---->| CCF 框架層 |
+---------------------+ +-------------------+↑|+---------------------+| 時鐘控制器驅動 || (如 fsl-imx8mp-ccm) |+---------------------+
三、CCF 核心數據結構分析
1. struct clk_hw
該結構體表示一個底層時鐘硬件實體,由時鐘提供者實現:
struct clk_hw {struct clk_core *core;struct clk_init_data *init;// 可擴展的私有數據指針
};
2. struct clk_ops
用于定義操作時鐘的函數集,是最關鍵的回調接口:
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
用于注冊時鐘時的初始化信息:
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
對象并添加至全局時鐘樹。
示例流程:
- 定義
clk_ops
實現; - 構造
clk_hw
; - 填寫
clk_init_data
; - 調用
devm_clk_hw_register()
完成注冊; - 若是 platform 設備,通過
of_clk_add_hw_provider()
提供給設備樹接口。
五、實戰開發:NXP i.MX8MP 時鐘驅動解析
我們以 NXP i.MX8MP 的 UART2 根時鐘 IMX8MP_CLK_UART2_ROOT
為例,講解從時鐘控制器提供者,到 UART2 驅動消費者的完整鏈路。
1. 設備樹配置分析
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)
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,
};
注冊流程:
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 的復合時鐘模型。
六、時鐘消費者如何使用時鐘
驅動中使用以下接口獲取與控制時鐘:
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. 示例平臺驅動結構:
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. 在設備樹中描述該時鐘:
demo_clk: demo-clk {compatible = "vendor,demo-gate-clk";#clock-cells = <0>;
};
3. 在其他設備中消費該時鐘:
my_peripheral@12340000 {...clocks = <&demo_clk>;clock-names = "core";
};
八、Framework 的統一管理機制
Linux 內核 CCF 框架通過 clk_core
全局對象池,維持整個時鐘拓撲結構,并提供統一接口進行操作與調試。
核心文件位于 drivers/clk/clk.c
,負責:
- 初始化時鐘樹結構;
- 實現
clk_get()
、clk_set_rate()
等函數; - 管理依賴關系(父子時鐘);
- 調度跨時鐘域變更時的同步機制。
框架的調試接口如:
cat /sys/kernel/debug/clk/clk_summary
可查看當前系統所有時鐘狀態。
九、總結與實戰建議
- Provider 注冊邏輯必須按 CCF 規則實現
clk_hw
、clk_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”