Linux 核心的 Common Clock Framework (CCF) 為 SoC 複雜的時鐘樹提供統一抽象。本文解析核心資料結構 struct clk、struct clk_ops 及 Samsung 專用的 clksrc_clk,並示範如何取得外部晶振頻率。
所有具體時鐘驅動需實作此介面,提供設定/讀取頻率、父時鐘切換等操作:
struct clk_ops {
int (*set_rate)(struct clk *c, unsigned long rate);
unsigned long (*get_rate)(struct clk *c);
unsigned long (*round_rate)(struct clk *c, unsigned long rate);
int (*set_parent)(struct clk *c, struct clk *parent);
};
每個時鐘節點在核心中對應一個 struct clk 實例:
struct clk {
struct list_head list; // 掛載至全域時鐘鏈表
struct module *owner; // 模組擁有者
struct clk *parent; // 父時鐘指標
const char *name; // 時鐘名稱(如 "mout_apll")
int id; // 識別碼
int usage; // 參考計數
unsigned long rate; // 當前頻率
unsigned long ctrlbit; // 控制暫存器位元
struct clk_ops *ops; // 操作函數集
int (*enable)(struct clk *, int enable); // 啟用/停用回調
};
新款 SoC 採用 Mux + Divider 架構,clksrc_clk 封裝此邏輯:
struct clksrc_sources {
unsigned int nr_sources;
struct clk **sources; // 可能的父時鐘陣列
};
struct clksrc_reg {
void __iomem *reg; // 暫存器虛擬位址
unsigned short shift; // 位元偏移
unsigned short size; // 位元寬度
};
struct clksrc_clk {
struct clk clk; // 繼承基礎結構
struct clksrc_sources *sources; // 來源清單
struct clksrc_reg reg_src; // 來源選擇暫存器
struct clksrc_reg reg_div; // 分頻器暫存器
};
以 clk_mout_apll 為例,其來源為 clk_src_apll(外部晶振或 APLL 輸出):
static struct clksrc_clk clk_mout_apll = {
.clk = { .name = "mout_apll", .id = -1 },
.sources = &clk_src_apll,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 0, .size = 1 },
};
static struct clk *clk_src_apll_list[] = {
[0] = &clk_fin_apll, // 外部晶振
[1] = &clk_fout_apll, // APLL 輸出
};
struct clksrc_sources clk_src_apll = {
.sources = clk_src_apll_list,
.nr_sources = ARRAY_SIZE(clk_src_apll_list),
};
// 統一定義外部晶振
#define clk_fin_apll clk_ext_xtal_mux
struct clk clk_ext_xtal_mux = { .name = "ext_xtal", .id = -1 };
驅動程式可透過標準 API 取得外部晶振頻率:
struct clk *xtal_clk = clk_get(NULL, "ext_xtal");
unsigned long xtal = clk_get_rate(xtal_clk);
clk_prepare_enable()/clk_disable_unprepare() 管理 usage,歸零時硬體才真正關閉。clksrc_clk 封裝 Mux/Div 硬體細節,驅動只需實作 clk_ops 即可複用核心邏輯。掌握此架構,可高效開發新 SoC 時鐘驅動,並利用 clkdev 查找機制讓裝置驅動無縫取得所需時鐘。