1. 架構介紹
Clock統是Linux內核中專門管理時鍾的子系統.
時鍾在嵌入式系統中很重要, 它就像人的脈搏一樣, 驅動器件工作.
任何一個CPU, 都需要給它提供一個外部晶振, 這個晶振就是用來提供時鍾的; 任何一個CPU內部的片上外設, 也需要工作時鍾: 例如GPIO控制器, 首先得給它提供工作時鍾, 然后才能訪問它的寄存器.
如果你去看一個ARM CPU的芯片手冊, 你一定能找到一個章節, 專門描述系統時鍾, 一般稱之為時鍾樹(clock tree).
芯片手冊從硬件的角度上描述了某個CPU的時鍾系統是如何設計的, 而Clock子系統從軟件的層面來抽象這個設計.
在本章中, 我們首先從硬件的角度來看看一個時鍾樹的的例子, 然后自己思考一下軟件層面該如何設計, 最后看看clock子系統是怎么做的.
2.1 時鍾樹
如今, 可運行Linux的主流CPU, 都有非常復雜的clock tree, 我們隨便拿一個處理器的spec, 查看clock相關的章節, 一定會有一個非常龐大和復雜的樹狀圖. 這個圖由clock相關的器件,以及這些器件輸出的clock組成.
下圖是一個簡單的示例:
clock相關的器件包括
用於產生clock的Oscillator(有源振盪器, 也稱作諧振盪器)或者Crystal(無源振盪器, 也稱晶振)
用於倍頻的PLL(鎖相環, Phase Locked Loop)
用於分頻的divider
用於多路選擇的Mux
用於clock enable控制的與門
使用clock的硬件模塊(可稱作consumer), 例如HW1, 它可能是GPIO控制器
器件用於產生具體的clock, 例如osc_clk. 這些器件的特性如下:
從輸入(parent)和輸出(children)的角度來看
某些器件沒有parent, 有一個或多個輸出. 例如osc_clk
某些器件有一個parent, 有一個或多個輸出. 例如PLL1, 它的parent是osc_clk, 輸出只有一個pll1_clk
某些器件有多個parent, 有一個或多個輸出. 例如MUX, 它有多個parent, 輸出只有一個hw3_clk
從頻率的角度上來看
某些clock的頻率是可以調整的, 我們可以通過設置倍頻和分頻因子來調整輸出頻率.
這一類clock最常見.
某些clock的頻率是固定的, 而且不能開關, 比如osc_clk. 最常見的是24M或25M.
這一類clock稱作fixed_rate_clock
某些clock的頻率也是固定的, 不過它可以開關.
這一類clock稱作gate_clock
某些clock有固定的倍頻和分頻因子, 它的輸出頻率跟隨parent的變化而變化.
這一類clock稱作fixed_factor_clock
在上圖的時鍾樹中, 有些是clock的提供者, 我們可以稱之為provider, 例如oscillator, PLLs; 有些是clock的使用者, 我們可以稱之為consumer, 例如HW1, HW2, HW3.
在ARM CPU的內部, 時鍾樹系統用來provide各種各樣的時鍾; 各片上外設consume這些時鍾. 例如時鍾樹系統負責提供時鍾給GPIO控制器, GPIO控制器則消費提供給它的工作時鍾.
在設備驅動開發的過程中, 我們經常會遇到的一個問題是: 想要開啟某個模塊的時鍾.
例如開發GPIO的驅動, 在驅動的probe函數中, 我們需要使能GPIO模塊的工作時鍾.
從軟件層面, 我們就是要提供一種機制, 讓consumer可以方便的獲取/使能/配置/關閉一個時鍾.
接下來, 我們看看軟件層面上該如何抽象.
2.2 軟件抽象
上一節我們介紹了時鍾樹, 並介紹了時鍾的provider和consumer. 一個CPU芯片內部, 會有很多個provider, 也會有很多的consumer. 軟件層面需要做的事情就是管理所有這些provider, 並向consumer提供盡量簡單的接口使得consumer可以獲取/使能/配置/關閉一個時鍾.
因此, 我們可以設計這樣一個池子, 所有的provider都可以向池子注冊, 把自己添加到池子里面. 池子里面可以用一個鏈表把所有的provider都串起來, 不同的provider以不同的name區分. 當consumer需要獲取某個clock的時候, 通過name向池子查詢即可.
在這個池子里面, 每一個provider都可以抽象成一個獨立的元素, 因此我們最好設計一個數據結構, 來表示每一個元素.
大致邏輯就是這樣了, Linux內核的clock子系統基本上就是在干這些事情.
2.3 clock子系統
Linux內核的clock子系統, 按照其職能, 可以大致分為3部分:
向下提供注冊接口, 以便各個clocks能注冊進clock子系統
在核心層維護一個池子, 管理所有注冊進來的clocks. 這一部分實現的是通用邏輯, 與具體硬件無關.
向上, 也就是像各個消費clocks的模塊的device driver, 提供獲取/使能/配置/關閉clock的通用API
clock子系統的結構框圖如下, 一些細節你現在可能還不能理解. 不過沒關系, 讀完后面的章節你就會明白了.
后面的章節, 我們也會分為上述3部分來描述, 在閱讀的過程中, 你可以邊看邊對着下面這張圖理解, 這樣會更加清晰.
2. clock provider -- 如何注冊Clocks
3.1 簡介
前文我們介紹了時鍾樹, 本章要闡述的主要問題就是如何把時鍾樹產生的這些clocks注冊進Linux內核的clock子系統. 換句話說, 就是如何編寫clock driver.
在ARM CPU內部, 管理時鍾樹的也是一個單獨的模塊, 一般叫PCM(Programmable Clock Management), 編寫clock driver其實就是編寫PCM的driver.
PCM也是CPU的一個片上外設, 因此它也會借用platform這套機制. 因此我們就需要有platform_device來描述設備, 同時要有與之對應的platform_driver來控制設備. 所謂控制, 就寫讀寫PCM的寄存器來使能/關閉時鍾, 設置時鍾頻率等等. 在platform_driver的probe函數中, 還有一項重要功能, 就是調用clock子系統提供的API, 向clock子系統注冊.
下面, 我看看編寫clock driver 的大致步驟是怎樣的.
3.2 編寫clock driver的大致步驟
由前文可知, 首先你得准備一個platform_device, 引入device tree的機制后, platform_device被dts替代了, 因此我們就需要在dts里面描述時鍾樹.
編寫platform_device(DTS node)
那么這個DTS該怎么寫呢? 通常有兩種方式:
方式一: 將時鍾樹中所有的clock,抽象為一個虛擬的設備,用一個DTS node表示.
1: /* arch/arm/boot/dts/exynos4210.dtsi */
2: theclock: clock-controller@0x10030000 {
3: compatible = "samsung,exynos4210-clock";
4: reg = <0x10030000 0x20000>;
5: #clock-cells = <1>;
6: };
這種方式跟編寫一個普通的片上外設的DTS很類似, 比如GPIO控制器的DTS, 對比一下, 是不是很類似.
從這個例子里面, 我們可以看出一個clock的DTS node的基本語法:
compatible , 決定了與這個node匹配的driver
reg就是用來描述PCM的寄存器
#clock-cells, 這個是clock provider node獨有的, 表明在引用此clock時, 需要用幾個32位來描述. 什么意思呢?
假設這個clock只有一個輸出時鍾, 那么#clock-cells = <0>, 我們在引用此clock的時候, 只用指明此clock即可.
引用此clock是什么意思? 引用指的是clock的consumer端. 例如GPIO模塊需要工作時鍾, 那么我們在編寫GPIO的DTS node時, 需要指明它的工作時鍾是多少, 這個過程就是引用, 寫個簡單的例子:
compatible = “yyyy”;
reg = <…. ….>;
……
clocks = <&theclock>; /* 指明/引用某一個clock */
}
假設這個clock有多個輸出時鍾, 那么#clock-cells = <0>肯定不行, 因為我們在引用此clock的時候, 需要指明到底用哪一個輸出時鍾.
這個時候#clock-cells 應該為 <1>, 在引用此clock, 就得這樣寫:
gpio : gpio-controller@xxxx {
compatible = “yyyy”;
reg = <…. ….>;
……
clocks = <&theclock num>; /* 指明/引用某一個clock, num是一個32位的整數, 表明到底用哪一個輸出clock */
}
theclock, 此DTS node的lable, clock consumer可以根據該名稱引用clock
方式二: 將時鍾樹中的每一個clock抽象為一個DTS node, 並以樹形的結構組織. 這種方式相較與方式一, 能更清晰的抽象出時鍾的樹形結構, 從邏輯上也更合理, 因此推薦大家使用這種方式.
舉個方式二的例子:
1: /* arch/arm/boot/dts/sun4i-a10.dtsi */
2: clocks {
3: #address-cells = <1>;
4: #size-cells = <1>;
5: ranges;
19: osc24M: osc24M@01c20050 {
20: #clock-cells = <0>;
21: compatible = "allwinner,sun4i-osc-clk";
22: reg = <0x01c20050 0x4>;
23: clock-frequency = <24000000>;
24: };
25:
27: #clock-cells = <0>;
28: compatible = "fixed-clock";
29: clock-frequency = <32768>;
30: };
31:
32: pll1: pll1@01c20000 {
33: #clock-cells = <0>;
34: compatible = "allwinner,sun4i-pll1-clk";
35: reg = <0x01c20000 0x4>;
36: clocks = <&osc24M>;
37: };
38:
39: /* dummy is 200M */
40: cpu: cpu@01c20054 {
41: #clock-cells = <0>;
42: compatible = "allwinner,sun4i-cpu-clk";
43: reg = <0x01c20054 0x4>;
44: clocks = <&osc32k>, <&osc24M>, <&pll1>, <&dummy>;
45: };
46:
47: axi: axi@01c20054 {
48: #clock-cells = <0>;
49: compatible = "allwinner,sun4i-axi-clk";
50: reg = <0x01c20054 0x4>;
51: clocks = <&cpu>;
52: };
53:
54: axi_gates: axi_gates@01c2005c {
55: #clock-cells = <1>;
56: compatible = "allwinner,sun4i-axi-gates-clk";
57: reg = <0x01c2005c 0x4>;
58: clocks = <&axi>;
59: clock-output-names = "axi_dram";
60: };
61:
62: ahb: ahb@01c20054 {
63: #clock-cells = <0>;
64: compatible = "allwinner,sun4i-ahb-clk";
65: reg = <0x01c20054 0x4>;
66: clocks = <&axi>;
67: };
68:
69: ahb_gates: ahb_gates@01c20060 {
70: #clock-cells = <1>;
71: compatible = "allwinner,sun4i-ahb-gates-clk";
72: reg = <0x01c20060 0x8>;
73: clocks = <&ahb>;
74: clock-output-names = "ahb_usb0", "ahb_ehci0",
75: "ahb_ohci0", "ahb_ehci1", "ahb_ohci1", "ahb_ss",
76: "ahb_dma", "ahb_bist", "ahb_mmc0", "ahb_mmc1",
77: "ahb_mmc2", "ahb_mmc3", "ahb_ms", "ahb_nand",
78: "ahb_sdram", "ahb_ace", "ahb_emac", "ahb_ts",
79: "ahb_spi0", "ahb_spi1", "ahb_spi2", "ahb_spi3",
80: "ahb_pata", "ahb_sata", "ahb_gps", "ahb_ve",
81: "ahb_tvd", "ahb_tve0", "ahb_tve1", "ahb_lcd0",
82: "ahb_lcd1", "ahb_csi0", "ahb_csi1", "ahb_hdmi",
83: "ahb_de_be0", "ahb_de_be1", "ahb_de_fe0",
84: "ahb_de_fe1", "ahb_mp", "ahb_mali400";
85: };
86:
87: apb0: apb0@01c20054 {
88: #clock-cells = <0>;
89: compatible = "allwinner,sun4i-apb0-clk";
90: reg = <0x01c20054 0x4>;
91: clocks = <&ahb>;
92: };
93:
94: apb0_gates: apb0_gates@01c20068 {
95: #clock-cells = <1>;
96: compatible = "allwinner,sun4i-apb0-gates-clk";
97: reg = <0x01c20068 0x4>;
98: clocks = <&apb0>;
99: clock-output-names = "apb0_codec", "apb0_spdif",
100: "apb0_ac97", "apb0_iis", "apb0_pio", "apb0_ir0",
101: "apb0_ir1", "apb0_keypad";
102: };
103:
104: /* dummy is pll62 */
105: apb1_mux: apb1_mux@01c20058 {
106: #clock-cells = <0>;
107: compatible = "allwinner,sun4i-apb1-mux-clk";
108: reg = <0x01c20058 0x4>;
109: clocks = <&osc24M>, <&dummy>, <&osc32k>;
110: };
111:
112: apb1: apb1@01c20058 {
113: #clock-cells = <0>;
114: compatible = "allwinner,sun4i-apb1-clk";
115: reg = <0x01c20058 0x4>;
116: clocks = <&apb1_mux>;
117: };
118:
119: apb1_gates: apb1_gates@01c2006c {
120: #clock-cells = <1>;
121: compatible = "allwinner,sun4i-apb1-gates-clk";
122: reg = <0x01c2006c 0x4>;
123: clocks = <&apb1>;
124: clock-output-names = "apb1_i2c0", "apb1_i2c1",
125: "apb1_i2c2", "apb1_can", "apb1_scr",
126: "apb1_ps20", "apb1_ps21", "apb1_uart0",
127: "apb1_uart1", "apb1_uart2", "apb1_uart3",
128: "apb1_uart4", "apb1_uart5", "apb1_uart6",
129: "apb1_uart7";
130: };
131: };
osc24M 代表24M的晶振
osc32k 代表32k的慢速時鍾
pll1 , 它的父時鍾是osc24M, 只有一個輸出時鍾
cpu , 它的父時鍾有多個<&osc32k>, <&osc24M>, <&pll1>, <&dummy>, 只有一個輸出時鍾
ahb_gates , 它的父時鍾只有一個, 是ahb, 但是它的輸出時鍾有很多個
...... 后面的都類似, 不一一說明了
看見了吧, 這種方式確實能清晰的描述多個clocks的樹形關系.
編寫platform_driver
有了platform_device之后, 接下來就得編寫platform_driver, 在driver里面最重要的事情就是向clock子系統注冊.
如何注冊呢?
clock子系統定義了clock driver需要實現的數據結構, 同時提供了注冊的函數. 我們只需要准備好相關的數據結構, 然后調用注冊函數進行注冊即可.
這些數據結構和接口函數的定義是在: include/linux/clk-provider.h
需要實現的數據結構是 struct clk_hw, 需要調用的注冊函數是struct clk *clk_register(struct device *dev, struct clk_hw *hw). 數據結構和注冊函數的細節我們在后文說明.
注冊函數會返回給你一個struct clk類型的指針, 在Linux的clock子系統中, 用一個struct clk代表一個clock. 你的CPU的時鍾樹里有多少個clock, 就會有多少個對應的struct clk.
當你拿到返回結果之后, 你需要調用另外一個API : of_clk_add_provider, 把剛剛拿到的返回結果通過此API丟到池子里面, 好讓consumer從這個池子里獲取某一個clock.
池子的概念我們在前文講述過, 除了上述方法, 你還可以用另外一種方法把struct clk添加到池子里面.
如果你要用這種方法, 你會用到clock子系統提供給你的另外幾個API, 這些API的定義在: include/linux/clkdev.h
其中最主要的一個API是int clk_register_clkdev(struct clk *, const char *, const char *, ...), 你可以在你的clock driver里面調用這個API, 把剛剛拿到的返回結果通過此API丟到池子里面.
為什么會存在這兩種方式呢? 得從consumer的角度來解答這個問題.
我們用GPIO來舉個例子, GPIO控制器需要工作時鍾, 這個時鍾假設叫gpio_clk, 它是一個provider. 你需要把這個provider注冊進clock子系統, 並把用於描述這個gpio_clk的struct clk添加到池子里面.
在GPIO控制器的driver代碼里, 我們需要獲取到gpio_clk這個時鍾並使能它, 獲取的過程就是向池子查詢.
怎么查詢? 你可以直接給定一個name, 然后通過這個name向池子查詢; 你也可以在GPIO的DTS node里面用clocks = <&theclock>;方式指明使用哪一個clock, 然后通過這種方式向池子查詢.
如果consumer是通過name查詢, 則對應的添加到池子的API就是clk_register_clkdev
如果consumer是通過DTS查詢, 則對應的添加到池子的API就是of_clk_add_provider
那么我在我的clock driver里面到底應該用哪個API向池子添加clk呢?
兩者你都應該同時使用, 這樣consumer端不管用哪種查詢方式都能工作.
讀到這里, 建議你回頭看看clock子系統的系統框圖, 結合框圖在琢磨琢磨.
接下來, 我們就會詳細介紹這些數據結構和相關的API了.
3.3 主要數據結構
通過前文, 我們知道了clock driver需要實現的一個主要的數據結構struct clk_hw.
與之相關的還有另外幾個重要數據結構: struct clk_init_data和struct clk_ops.
下面我們看看這幾個數據結構.
struct clk_hw
頭文件: include/linux/clk-provider.h
struct clk_hw |
Comment |
struct clk_core *core |
clk_core是clock子系統核心層的一個數據結構, 由核心層代碼創建和維護, 一個clk_core對應一個具體的clock. |
struct clk *clk |
clk也是clock子系統核心層的一個數據結構, 同樣由核心層代碼創建和維護, 它也對應一個具體的clock. clk_core和clk的細節, 放在《clock core》一章中描述 |
const struct clk_init_data *init |
clk_init_data是provider需要實現並填充的一個數據結構, 編寫clock driver, 最主要的任務就是實現它 |
struct clk_init_data
頭文件: include/linux/clk-provider.h
struct clk_init_data |
Comment |
const char*name |
該clock的name, 系統中會存在很多個clock, 每一個clock都會有一個名稱, 可以用名稱來區分不同的clock, 因此不能重名 |
const struct clk_ops*ops |
與clock控制相關的ops, 例如enable/disable; set_rate等等. 當consumer端想要操作某個clock時, 最終就會調用到該clock的clk_ops |
const char**parent_names |
該clock的所有parents. 通過name, 就能找到parent是誰了 |
u8num_parents |
一個clock可能有多個parents, num_parents指明到底有幾個 |
unsigned longflags |
標志位, 表明該clock的一些特性, 核心層代碼會根據不同的flags采取不同的動作. 可選的值如下: #define CLK_SET_RATE_GATEBIT(0) /* must be gated across rate change */ #define CLK_SET_PARENT_GATEBIT(1) /* must be gated across re-parent */ #define CLK_SET_RATE_PARENTBIT(2) /* propagate rate change up one level */ #define CLK_IGNORE_UNUSEDBIT(3) /* do not gate even if unused */ #define CLK_IS_ROOTBIT(4) /* root clk, has no parent */ #define CLK_IS_BASICBIT(5) /* Basic clk, can't do a to_clk_foo() */ #define CLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */ #define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */ #define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */ |
struct clk_ops
頭文件: include/linux/clk-provider.h
下述這些ops在.h文件里面都有詳細的注釋, 可以閱讀源代碼獲取更多信息.
struct clk_ops |
Comment |
int(*prepare)(struct clk_hw *hw) |
為什么要有prepare接口, 直接enable不就行了嗎? 從硬件的角度來說, 某些clock, 如果想使能它, 需要等待一段時間. 例如倍頻器PLL, 當你使能倍頻器之后, 你需要等待幾毫秒讓倍頻器工作平穩. 因為要等待, 軟件上就有可能sleep, 也就是休眠. 但是Linux內核中有很多情況下不能休眠, 比如說中斷服務器程序. 如果你把所有的操作都放在enable這一個函數里面, 那么enable函數就可能休眠, 因而中斷服務程序里面就不能調用enable函數. 但實際情況是, 很多時候, 我們都要求在中斷服務程序里面開/關某個clock 怎么辦呢? 拆分成2個函數, prepare和enable. prepare負責使能clock之前的准備工作, prepare里面可以休眠, 一旦prepare返回, 就意味着clock已經完全准備好了, 可以直接開/關 enable負責打開clock, 它不能休眠, 這樣在中斷服務程序中也可以調用enable了 |
void(*unprepare)(struct clk_hw *hw) |
prepare的反函數, un-do prepare里面做的所有事情 |
int(*is_prepared)(struct clk_hw *hw) |
is_prepared, 判斷clock是否已經prepared, 可以不提供. clock framework core會維護一個prepare的計數(該計數在clk_prepare調用時加一, 在clk_unprepare時減一), 並依據該計數判斷是否prepared |
void(*unprepare_unused)(struct clk_hw *hw) |
自動unprepare unused clocks clock framework core提供一個clk_disable_unused接口, 在系統初始化的late_call中調用, 用於關閉unused clocks, 這個接口會調用相應clock的.unprepare_unused和.disable_unused函數 |
int(*enable)(struct clk_hw *hw) |
使能clock, 此函數不能休眠 當此函數返回時, 代表consumer端可以收到一個穩定, 可用的clock波形了. |
void(*disable)(struct clk_hw *hw) |
禁止clock, 此函數不能休眠 |
int(*is_enabled)(struct clk_hw *hw) |
與is_prepared類似 |
void(*disable_unused)(struct clk_hw *hw) |
與unprepare_unused類似 |
int(*save_context)(struct clk_hw *hw) |
Save the context of the clock in prepration for poweroff |
void(*restore_context)(struct clk_hw *hw) |
Restore the context of the clock after a restoration of power |
unsigned long(*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate) |
以parent clock rate為參數, 從新計算並返回clock rate |
long(*round_rate)(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) |
Given a target rate as input, returns the closest rate actually supported by the clock 該接口有點特別, 在返回rounded rate的同時, 會通過一個指針, 返回round后parent的rate. 這和CLK_SET_RATE_PARENT flag有關
當clock consumer調用clk_round_rate獲取一個近似的rate時, 如果該clock沒有提供.round_rate函數, 有兩種方法: 在沒有設置CLK_SET_RATE_PARENT標志時, 直接返回該clock的cache rate 如果設置了CLK_SET_RATE_PARENT標志, 則會詢問parent, 即調用clk_round_rate獲取parent clock能提供的、最接近該rate的值. 這是什么意思呢? 也就是說, 如果parent clock可以得到一個近似的rate值, 那么通過改變parent clock, 就能得到所需的clock |
long(*determine_rate)(struct clk_hw *hw, unsigned long rate, unsigned long min_rate, unsigned long max_rate, unsigned long *best_parent_rate, struct clk_hw **best_parent_hw) |
與round_rate類似, 暫時不清楚它倆有什么區別. |
int(*set_parent)(struct clk_hw *hw, u8 index) |
Change the input source of this clock 有的clocks可能有多個parents, 那到底用哪一個呢? 由參數index決定 |
u8(*get_parent)(struct clk_hw *hw) |
Queries the hardware to determine the parent of a clock 返回值是一個u8類型的變量, 它是一個index, 通過它可以查找到對應的parent. 所有的parents都存儲在.parent_names or .parents arrays里面
實際上, 此函數會讀取硬件寄存器, 然后把寄存器的值轉變為對應的index |
int(*set_rate)(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) |
Change the rate of this clock The requested rate is specified by the second argument, which should typically be the return of .round_rate call
The third argument gives the parent rate which is likely helpful for most .set_rate implementation |
int (*set_rate_and_parent)(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate, u8 index) |
Change the rate and the parent of this clock This callback is optional (and unnecessary) for clocks with 0 or 1 parents as well as for clocks that can tolerate switching the rate and the parent separately via calls to .set_parent and .set_rate |
unsigned long (*recalc_accuracy)(struct clk_hw *hw, unsigned long parent_accuracy) |
Recalculate the accuracy of this clock The clock accuracy is expressed in ppb (parts per billion) |
int(*get_phase)(struct clk_hw *hw) |
Queries the hardware to get the current phase of a clock Returned values are 0-359 degrees on success, negative error codes on failure |
int(*set_phase)(struct clk_hw *hw, int degrees) |
Shift the phase this clock signal in degrees specified by the second argument Valid values for degrees are 0-359 Return 0 on success, otherwise -EERROR |
void(*init)(struct clk_hw *hw) |
Perform platform-specific initialization magic 不過從代碼注釋來看, 內核推薦你不要實現這個接口函數, 后面可能會遺棄 |
int(*debug_init)(struct clk_hw *hw, struct dentry *dentry) |
debugfs相關, 這里不細述 細節可以看代碼注釋 |
3.4 主要API說明
Linux clock子系統向下提供幾個重要的API, 下面我挨個看下這些API的細節.
clk_register / devm_clk_register
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk.c
/**
* clk_register - allocate a new clock, register it and return an opaque cookie
* @dev: device that is registering this clock
* @hw: link to hardware-specific clock data
*
* clk_register is the primary interface for populating the clock tree with new
* clock nodes. It returns a pointer to the newly allocated struct clk which
* cannot be dereferenced by driver code but may be used in conjuction with the
* rest of the clock API. In the event of an error clk_register will return an
* error code; drivers must test for an error code after calling clk_register.
*/
struct clk *clk_register(struct device *dev, struct clk_hw *hw);
struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw);
void clk_unregister(struct clk *clk);
void devm_clk_unregister(struct device *dev, struct clk *clk);
clk_register是clock子系統提供的注冊clock的最基礎的API函數, 后文描述的其它APIs都是對它的封裝.
devm_clk_register是clk_register的devm版本, devm機制在《設備模型》一文中有詳述.
要向系統注冊一個clock也很簡單, 准備好clk_hw結構體, 然后調用clk_register接口即可.
不過, clock framework所做的遠比這周到, 它基於clk_register, 又封裝了其它接口, 在向clock子系統注冊時, 連struct clk_hw都不需要關心, 而是直接使用類似人類語言的方式.
也就是說, 實際在編寫clock driver的時候, 我們不會直接使用clk_register接口, 只需要調用下面的某個API即可.
下文我們一一介紹這些API.
clk_register_fixed_rate
clock有不同的類型, 有的clock頻率是固定的, 不可調整的, 例如外部晶振, 頻率就是固定的(24M / 25M). 這種類型的clock就是fixed_rate clock.
fixed_rate clock具有固定的頻率, 不能開關、不能調整頻率、不能選擇parent、不需要提供任何的clk_ops回調函數, 是最簡單的一類clock.
如果要注冊這種類型的clock, 直接調用本API, 傳遞相應的參數給本API即可. API的細節如下:
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk-fixed-rate.c
/*
* DOC: Basic clock implementations common to many platforms
*
* Each basic clock hardware type is comprised of a structure describing the
* clock hardware, implementations of the relevant callbacks in struct clk_ops,
* unique flags for that hardware type, a registration function and an
* alternative macro for static initialization
*/
/**
* struct clk_fixed_rate - fixed-rate clock
* @hw: handle between common and hardware-specific interfaces
* @fixed_rate: constant frequency of clock
*/
struct clk_fixed_rate {
struct clk_hw hw;
unsigned long fixed_rate;
unsigned long fixed_accuracy;
u8 flags;
};
extern const struct clk_ops clk_fixed_rate_ops;
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate);
struct clk *clk_register_fixed_rate_with_accuracy(struct device *dev,
const char *name, const char *parent_name, unsigned long flags,
unsigned long fixed_rate, unsigned long fixed_accuracy);
void of_fixed_clk_setup(struct device_node *np);
若想注冊一個fixed rate clock, 除了你可以在自己的clock driver里面手動調用clk_register_fixed_rate之外, 還有一種更簡單的方式, 那就是用DTS.
你可以在DTS里面用如下方式描述一個fixed rate clock:
26: osc32k: osc32k {
27: #clock-cells = <0>;
28: compatible = "fixed-clock";
29: clock-frequency = <32768>;
clock-accuracy = xxxx;
clock-output-names = xxxx;
30: };
關鍵地方在於compatible一定要是”fixed-clock”
“drivers/clk/clk-fixed-rate.c”中的of_fixed_clk_setup會負責匹配這個compatible, 這個C文件是clock子系統實現的.
of_fixed_clk_setup會解析3個參數: clock-frequency, clock-accuracy, clock-output-names
clock-frequency是必須的, 另外2個參數可選.
參數解析完畢之后, 會調用clk_register_fixed_rate_with_accuracy向系統注冊一個clock.
clk_register_gate
這一類clock只可開關(會提供.enable/.disable回調), 可使用下面接口注冊:
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk-gate.c
/**
* struct clk_gate - gating clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling gate
* @bit_idx: single bit controlling gate
* @flags: hardware-specific flags
* @lock: register lock
*
* Clock which can gate its output. Implements .enable & .disable
*
* Flags:
* CLK_GATE_SET_TO_DISABLE - by default this clock sets the bit at bit_idx to
* enable the clock. Setting this flag does the opposite: setting the bit
* disable the clock and clearing it enables the clock
* CLK_GATE_HIWORD_MASK - The gate settings are only in lower 16-bit
* of this register, and mask of gate bits are in higher 16-bit of this
* register. While setting the gate bits, higher 16-bit should also be
* updated to indicate changing gate bits.
*/
struct clk_gate {
struct clk_hw hw;
void __iomem *reg;
u8 bit_idx;
u8 flags;
spinlock_t *lock;
};
#define CLK_GATE_SET_TO_DISABLE BIT(0)
#define CLK_GATE_HIWORD_MASK BIT(1)
extern const struct clk_ops clk_gate_ops;
struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock);
void clk_unregister_gate(struct clk *clk);
clk_register_gate, 它的參數列表如下:
name: clock的名稱
parent_name: parent clock的名稱, 沒有的話可留空
flags : 參考3.3節 struct clk_hw結構體中對於flags的描述
reg: 控制該clock開關的寄存器地址(虛擬地址)
bit_idx: reg中, 第幾個bit是控制clock開/關的
clk_gate_flags: gate clock特有的參數. 其中一個可選值是CLK_GATE_SET_TO_DISABLE, 它的意思是1表示開還是0表示開, 類似於翻轉位.
lock: 如果clock開關時需要互斥, 可提供一個spinlock.
clock子系統並沒有定義類似fixed rate clock的DTS處理方式.
如果你想借用DTS, 也很簡單. 如下:
ehrpwm1_tbclk: ehrpwm1_tbclk@44e10664 {
#clock-cells = <0>;
compatible = "ti,gate-clock";
clocks = <&l4ls_gclk>;
ti,bit-shift = <1>;
reg = <0x0664>;
};
注意它的compatible, 自己實現一個driver, 匹配這個compatible. 然后在driver里面解析DTS相關參數並調用clk_register_gate API 即可.
clk_register_divider / clk_register_divider_table
這一類clock可以設置分頻值(因而會提供.recalc_rate/.set_rate/.round_rate回調), 可通過下面兩個接口注冊:
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk-divider.c
/**
* struct clk_divider - adjustable divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register containing the divider
* @shift: shift to the divider bit field
* @width: width of the divider bit field
* @table: array of value/divider pairs, last entry should have div = 0
* @lock: register lock
*
* Clock with an adjustable divider affecting its output frequency. Implements
* .recalc_rate, .set_rate and .round_rate
*
* Flags:
* CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the
* register plus one. If CLK_DIVIDER_ONE_BASED is set then the divider is
* the raw value read from the register, with the value of zero considered
* invalid, unless CLK_DIVIDER_ALLOW_ZERO is set.
* CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from
* the hardware register
* CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors. For dividers which have
* CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor.
* Some hardware implementations gracefully handle this case and allow a
* zero divisor by not modifying their input clock
* (divide by one / bypass).
* CLK_DIVIDER_HIWORD_MASK - The divider settings are only in lower 16-bit
* of this register, and mask of divider bits are in higher 16-bit of this
* register. While setting the divider bits, higher 16-bit should also be
* updated to indicate changing divider bits.
* CLK_DIVIDER_ROUND_CLOSEST - Makes the best calculated divider to be rounded
* to the closest integer instead of the up one.
* CLK_DIVIDER_READ_ONLY - The divider settings are preconfigured and should
* not be changed by the clock framework.
*/
struct clk_divider {
struct clk_hw hw;
void __iomem *reg;
u8 shift;
u8 width;
u8 flags;
const struct clk_div_table *table;
spinlock_t *lock;
u32 context;
};
#define CLK_DIVIDER_ONE_BASED BIT(0)
#define CLK_DIVIDER_POWER_OF_TWO BIT(1)
#define CLK_DIVIDER_ALLOW_ZERO BIT(2)
#define CLK_DIVIDER_HIWORD_MASK BIT(3)
#define CLK_DIVIDER_ROUND_CLOSEST BIT(4)
#define CLK_DIVIDER_READ_ONLY BIT(5)
extern const struct clk_ops clk_divider_ops;
unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
unsigned int val, const struct clk_div_table *table,
unsigned long flags);
long divider_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate, const struct clk_div_table *table,
u8 width, unsigned long flags);
int divider_get_val(unsigned long rate, unsigned long parent_rate,
const struct clk_div_table *table, u8 width,
unsigned long flags);
struct clk *clk_register_divider(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_divider_flags, spinlock_t *lock);
struct clk *clk_register_divider_table(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_divider_flags, const struct clk_div_table *table,
spinlock_t *lock);
void clk_unregister_divider(struct clk *clk);
clk_register_divider : 該接口用於注冊分頻比規則的clock
reg: 控制clock分頻比的寄存器
shift: 控制分頻比的bit在寄存器中的偏移
width: 控制分頻比的bit位數, 默認情況下, 實際的divider值是寄存器值加1.
如果有其它例外, 可使用下面的的flag指示。
clk_divider_flags: divider clock特有的flag, 包括: CLK_DIVIDER_ONE_BASED:
實際的divider值就是寄存器值(0是無效的,除非設置CLK_DIVIDER_ALLOW_ZERO flag)
CLK_DIVIDER_POWER_OF_TWO:
實際的divider值是寄存器值得2次方
CLK_DIVIDER_ALLOW_ZERO:
divider值可以為0(不改變,視硬件支持而定)
clk_register_divider_table : 該接口用於注冊分頻比不規則的clock,和上面接口比較,差別在於divider值和寄存器值得對應關系由一個table決定. table的原型如下:
struct clk_div_table {
unsigned int val;
unsigned int div;
};
val代表寄存器值, div代表對應的分頻值.
同樣, clock子系統並沒有實現DTS相關接口, 不過你可以自己編寫driver去解析DTS並調用API注冊.
clk_register_mux
這一類clock可以選擇多個parent, 因為會實現.get_parent/.set_parent/.recalc_rate回調, 可通過下面兩個接口注冊:
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk-mux.c
/**
* struct clk_mux - multiplexer clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling multiplexer
* @shift: shift to multiplexer bit field
* @width: width of mutliplexer bit field
* @flags: hardware-specific flags
* @lock: register lock
*
* Clock with multiple selectable parents. Implements .get_parent, .set_parent
* and .recalc_rate
*
* Flags:
* CLK_MUX_INDEX_ONE - register index starts at 1, not 0
* CLK_MUX_INDEX_BIT - register index is a single bit (power of two)
* CLK_MUX_HIWORD_MASK - The mux settings are only in lower 16-bit of this
* register, and mask of mux bits are in higher 16-bit of this register.
* While setting the mux bits, higher 16-bit should also be updated to
* indicate changing mux bits.
* CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired
* frequency.
*/
struct clk_mux {
struct clk_hw hw;
void __iomem *reg;
u32 *table;
u32 mask;
u8 shift;
u8 flags;
spinlock_t *lock;
u8 saved_parent;
};
#define CLK_MUX_INDEX_ONE BIT(0)
#define CLK_MUX_INDEX_BIT BIT(1)
#define CLK_MUX_HIWORD_MASK BIT(2)
#define CLK_MUX_READ_ONLY BIT(3) /* mux can't be changed */
#define CLK_MUX_ROUND_CLOSEST BIT(4)
extern const struct clk_ops clk_mux_ops;
extern const struct clk_ops clk_mux_ro_ops;
struct clk *clk_register_mux(struct device *dev, const char *name,
const char **parent_names, u8 num_parents, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_mux_flags, spinlock_t *lock);
struct clk *clk_register_mux_table(struct device *dev, const char *name,
const char **parent_names, u8 num_parents, unsigned long flags,
void __iomem *reg, u8 shift, u32 mask,
u8 clk_mux_flags, u32 *table, spinlock_t *lock);
void clk_unregister_mux(struct clk *clk);
clk_register_mux : 該接口可注冊mux控制比較規則的clock(類似divider clock)
parent_names: 一個字符串數組,用於描述所有可能的parent clock
num_parents: parent clock的個數
reg、shift、width: 選擇parent的寄存器、偏移、寬度
clk_mux_flags: mux clock特有的flag
CLK_MUX_INDEX_ONE : 寄存器值不是從0開始,而是從1開始
CLK_MUX_INDEX_BIT : 寄存器值為2的冪
clk_register_mux_table : 該接口通過一個table, 注冊mux控制不規則的clock, 原理和divider clock類似, 不再詳細介紹
同樣, clock子系統並沒有實現DTS相關接口, 不過你可以自己編寫driver去解析DTS並調用API注冊.
clk_register_fixed_factor
這一類clock具有固定的factor(即multiplier和divider), clock的頻率是由parent clock的頻率, 乘以mul, 除以div, 多用於一些具有固定分頻系數的clock.
由於parent clock的頻率可以改變, 因而fix factor clock也可以改變頻率, 因此也會提供.recalc_rate/.set_rate/.round_rate等回調.
可通過下面接口注冊:
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk-fixed-factor.c
void of_fixed_factor_clk_setup(struct device_node *node);
/**
* struct clk_fixed_factor - fixed multiplier and divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @mult: multiplier
* @div: divider
*
* Clock with a fixed multiplier and divider. The output frequency is the
* parent clock rate divided by div and multiplied by mult.
* Implements .recalc_rate, .set_rate and .round_rate
*/
struct clk_fixed_factor {
struct clk_hw hw;
unsigned int mult;
unsigned int div;
};
extern struct clk_ops clk_fixed_factor_ops;
struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned int mult, unsigned int div);
API 參數比較簡單, 不多說了.
另外, clock子系統還提供了此種類型clock的DTS接口.
clk-fixed-factor.c中的of_fixed_factor_clk_setup函數會負責解析DTS. 關於DTS的匹配規則和相關參數, 自己看看源碼吧.
clk_register_fractional_divider
這一類和divider clock很像, 唯一的不同在於它可支持到更細的粒度 (可用小數表示分頻因子) . 例如 clk = parent / 1.5
API說明如下:
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk-fractional-divider.c
/**
* struct clk_fractional_divider - adjustable fractional divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register containing the divider
* @mshift: shift to the numerator bit field
* @mwidth: width of the numerator bit field
* @nshift: shift to the denominator bit field
* @nwidth: width of the denominator bit field
* @lock: register lock
*
* Clock with adjustable fractional divider affecting its output frequency.
*/
struct clk_fractional_divider {
struct clk_hw hw;
void __iomem *reg;
u8 mshift;
u32 mmask;
u8 nshift;
u32 nmask;
u8 flags;
spinlock_t *lock;
};
extern const struct clk_ops clk_fractional_divider_ops;
struct clk *clk_register_fractional_divider(struct device *dev,
const char *name, const char *parent_name, unsigned long flags,
void __iomem *reg, u8 mshift, u8 mwidth, u8 nshift, u8 nwidth,
u8 clk_divider_flags, spinlock_t *lock);
clk_register_composite
顧名思義, 就是mux、divider、gate等clock的組合, 可通過下面接口注冊:
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk-composite.c
/***
* struct clk_composite - aggregate clock of mux, divider and gate clocks
*
* @hw: handle between common and hardware-specific interfaces
* @mux_hw: handle between composite and hardware-specific mux clock
* @rate_hw: handle between composite and hardware-specific rate clock
* @gate_hw: handle between composite and hardware-specific gate clock
* @mux_ops: clock ops for mux
* @rate_ops: clock ops for rate
* @gate_ops: clock ops for gate
*/
struct clk_composite {
struct clk_hw hw;
struct clk_ops ops;
struct clk_hw *mux_hw;
struct clk_hw *rate_hw;
struct clk_hw *gate_hw;
const struct clk_ops *mux_ops;
const struct clk_ops *rate_ops;
const struct clk_ops *gate_ops;
};
struct clk *clk_register_composite(struct device *dev, const char *name,
const char **parent_names, int num_parents,
struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
unsigned long flags);
看着有點復雜, 但理解了上面1~5類clock, 這里就只剩下苦力了, 耐心一點,就可以了.
另外, clock子系統沒有提供相關的DTS解析函數.
clk_register_gpio_gate
把某個gpio當做一個gate clock. 也就是說可以通過clock子系統來控制這個gpio 開/關, 也就是控制這個GPIO輸出高/低電平.
某些情況下, 有的模塊需要通過某個GPIO來控制其使能/禁止, 例如藍牙模塊. 而且你也不需要向此模塊提供時鍾, 模板本身就有晶振存在, 可以自己給自己供時鍾.
這個時候我們就可以把該GPIO抽象成gpio gate clock, 借助clock子系統, 來控制模塊的enable/disable.
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk-gpio-gate.c
/***
* struct clk_gpio_gate - gpio gated clock
*
* @hw: handle between common and hardware-specific interfaces
* @gpiod: gpio descriptor
*
* Clock with a gpio control for enabling and disabling the parent clock.
* Implements .enable, .disable and .is_enabled
*/
struct clk_gpio {
struct clk_hw hw;
struct gpio_desc *gpiod;
};
extern const struct clk_ops clk_gpio_gate_ops;
struct clk *clk_register_gpio_gate(struct device *dev, const char *name,
const char *parent_name, unsigned gpio, bool active_low,
unsigned long flags);
void of_gpio_clk_gate_setup(struct device_node *node);
API 參數比較簡單, 不多說了.
另外, clock子系統還提供了此種類型clock的DTS接口.
xxx_unregister
上述xxx_register函數都有對應的xxx_unregister.
unregister的函數原型在上述代碼中都有提及, 這里不多說了, 有興趣可以自行閱讀源代碼.
clk_register_clkdev
頭文件: include/linux/clkdev.h
實現文件: drivers/clk/clkdev.c
原型: int clk_register_clkdev(struct clk *, const char *, const char *, ...);
上述的clk_register_xxx接口會向clock子系統注冊一個clock, 並返回一個代表該clock的struct clk結構體.
clk_register_clkdev的作用就是把返回的這個struct clk添加到某個池子里面.
這個池子其實就是個鏈表啦, 鏈表定義在clkdev.c里面.
池子里面主要是用name做為關鍵字, 區分不同的clk.
當consumer端想要查詢某個clk的時候, 就會嘗試從這個鏈表里面通過clock name去檢索.
of_clk_add_provider
頭文件: include/linux/clk-provider.h
實現文件: drivers/clk/clk.c
原型:
int of_clk_add_provider(struct device_node *np,
struct clk *(*clk_src_get)(struct of_phandle_args *args,
void *data),
void *data);
此API的主要目的也是把得到的struct clk結構體添加到某個池子里面.
這個池子也是個鏈表, 定義在clk.c里面.
與上面那個池子的不同之處在於, 這里是用device_node做為關鍵字來區分不同的clk.
當consumer端想要查詢某個clk的時候, 會在DTS node里面通過clocks = <&xxxx>來引用某個clock.
通過引用的這個clock的phandle, 就能找到對應的device_node. 然后通過device_node就能從池子里面檢索出需要的clk.
其實, 這兩種池子對應了我們的consumer端的兩種方式: 一種是通過name獲取clk, 不需要DTS; 另外一種就是通過DTS.
隨着Linux內核大力推行DTS, 方式二會逐漸成為主流.
3. clock core -- 如何管理Clocks
4.1 簡介
第3章我們描述了clock子系統的功能之一 : 向底層的clock driver提供注冊接口.
本章我們描述clock子系統的功能之二 : 如何管理這些clocks.
當clock driver向子系統注冊某一個clock的時候, 子系統內部會創建一個數據結構來表示這個clock. 這個數據結構在前文提過, 就是struct clk.
一個struct clk就對應一個clock. 到目前為止, 我們還沒見過這個數據結構的廬山真面目呢! 本章會圍繞這個數據結構展開, 充分理解它, 你就里面本章了.
4.2 主要數據結構
struct clk
結構體定義在一個C文件里面 : drivers/clk/clk.c
struct clk |
Comment |
struct clk_core*core |
clk_core是clock核心層的一個私有數據結構, 下面細述 |
const char *dev_id |
使用該clk的device的name |
const char *con_id |
connection ID string on device |
unsigned long min_rate |
最小rate |
unsigned long max_rate |
最大rate |
struct hlist_node clks_node |
這個結構體有點顛覆本文的一個觀點, 那就是: 一個struct clk結構體對應一個具體的clock.
仔細閱讀源碼后發現, 原來每當某一個driver嘗試獲取某個clock的時候, clock子系統就會創建一個struct clk. 例如假設有個clock, 名稱是pll_clk. GPIO, SPI, I2C這3個模塊都是用此pll_clk做為工作時鍾的. 那么clock子系統核心層會有一個clk_core用於描述pll_clk, 每當GPIO/SPI/I2C的driver首次嘗試獲取pll_clk時, clock子系統就會創建一個struct clk結構體, 並將創建的這個struct clk返回給對應的driver. 該clk結構體的dev_id和con_id都是對應的driver指定的.
不過為了簡便起見, 我們在本文中還是暫定一個struct clk對應一個clock吧, 理解起來不會有什么異常. |
struct clk_core
前文我們說過, 一個struct clk就代表一個clock. 一個clk_core與clock也是一一對應的關系. 那它倆有什么區別呢?
struct clk更像是對外的接口, 例如當provider向clock子系統注冊時, 它會得到一個struct clk*的返回結果; 當consumer想要使用某個clock時, 也會首先獲取struct clk*這個結構體.
struct clk_core則是clock子系統核心層的一個私有數據結構, 在核心層代描述某個具體的clock. provider或consumer不會觸碰這個數據結構.
結構體定義在一個C文件里面 : drivers/clk/clk.c
struct clk_core |
Comment |
const char*name |
此clock的name, provider在注冊的時候會提供clock的name |
const struct clk_ops*ops |
操作此clock的ops, provider在注冊的時候會提供clock的ops |
struct clk_hw*hw |
provider端提供的clk_hw結構體 |
struct module*owner |
|
struct clk_core*parent |
一個clock可能有多個parent, 但是同一時刻只能有一個parent有效. 這里指向有效的那個父時鍾所對應的clk_core |
const char**parent_names |
所有可選的父時鍾的names |
struct clk_core**parents |
所有可選的父時鍾的clk_core |
u8num_parents |
有多少個parents |
u8new_parent_index |
你可以把所有的parents理解成一個數組, index就對應數組的某個元素 |
unsigned longrate |
clock的rate |
unsigned longreq_rate |
consumer端要求的rate |
unsigned longnew_rate |
新的rate |
struct clk_core*new_parent |
新的parent |
struct clk_core*new_child |
新的child |
unsigned longflags |
此clock的flags, 可選的flag在3.3節《struct clk_init_data》中有介紹 |
unsigned intenable_count |
被使能的次數 |
unsigned intprepare_count |
被prepare的次數 |
unsigned longaccuracy |
此clock當前的accuracy, The clock accuracy is expressed in ppb (parts per billion) |
intphase |
此clock當前的phase, 取值范圍 0 - 359 |
struct hlist_headchildren |
鏈表頭, 用於掛接本clock所有的children |
struct hlist_nodechild_node |
鏈表節點, 用於把本clock掛接到對應的parent的children鏈表下 |
struct hlist_nodedebug_node |
鏈表節點, 與debugfs相關 |
struct hlist_headclks |
一個struct clk_core可能對應多個struct clk, 這個鏈表頭掛接所有的clks. 關於struct clk_core和struct clk的關系, 參見4.2節《struct clk》的說明 |
unsigned intnotifier_count |
內核通知鏈機制相關. 當內核其它模塊想知道某個clock的變化時 (例如頻率等的改變), 可以向此clock的通知鏈注冊. 這樣當clock發生變化時, 就會向通知鏈上的所有模塊發送通知 notifier_count代表有多少個模塊向本clock注冊了 |
struct dentry*dentry |
debugfs相關 |
struct krefref |
引用計數 |
4.3 關鍵代碼分析
clk_register
我們在3.4節介紹了clock子系統提供給provider端的總多clk_register_xxx函數. 這些函數都是對clk_register的封裝, 基礎都是clk_register.
因此我們本章只重點講解這一個API, 其它API有興趣可以自己閱讀源碼.
在開始分析之前, 結合之前講解的內容, 你能猜到clk_register會做哪些事情嗎?
首先, 它得創建struct clk和struct clk_core這兩個數據結構.
然后, 它得維護clk_core的樹形關系, 就像時鍾數的硬件形態那樣:
有一個或多個ROOT_CLOCK, ROOT_CLOCK沒有parent, 下面掛載的是children, children下面在掛載children.
為什么要維護這樣的樹形結構呢? 因為我們在操作某個clk時, 往往會跟它的parent有關: 例如要使能某個clk, 必須保證它的parent也使能; 或者parent的rate改變了, 那它的children的rate都有可能改變. 因此clock子系統必須維護好樹形結構, 才能方便的處理這些相關性.
下面我們來看看代碼:
實現文件: drivers/clk/clk.c
/**
* clk_register - allocate a new clock, register it and return an opaque cookie
* @dev: device that is registering this clock
* @hw: link to hardware-specific clock data
*
* clk_register is the primary interface for populating the clock tree with new
* clock nodes. It returns a pointer to the newly allocated struct clk which
* cannot be dereferenced by driver code but may be used in conjuction with the
* rest of the clock API. In the event of an error clk_register will return an
* error code; drivers must test for an error code after calling clk_register.
*/
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
int i, ret;
struct clk_core *clk;
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
if (!clk) {
pr_err("%s: could not allocate clk\n", __func__);
ret = -ENOMEM;
goto fail_out;
}
clk->name = kstrdup_const(hw->init->name, GFP_KERNEL);
if (!clk->name) {
pr_err("%s: could not allocate clk->name\n", __func__);
ret = -ENOMEM;
goto fail_name;
}
clk->ops = hw->init->ops;
if (dev && dev->driver)
clk->owner = dev->driver->owner;
clk->hw = hw;
clk->flags = hw->init->flags;
clk->num_parents = hw->init->num_parents;
hw->core = clk;
/* allocate local copy in case parent_names is __initdata */
clk->parent_names = kcalloc(clk->num_parents, sizeof(char *),
GFP_KERNEL);
if (!clk->parent_names) {
pr_err("%s: could not allocate clk->parent_names\n", __func__);
ret = -ENOMEM;
goto fail_parent_names;
}
/* copy each string name in case parent_names is __initdata */
for (i = 0; i < clk->num_parents; i++) {
clk->parent_names[i] = kstrdup_const(hw->init->parent_names[i],
GFP_KERNEL);
if (!clk->parent_names[i]) {
pr_err("%s: could not copy parent_names\n", __func__);
ret = -ENOMEM;
goto fail_parent_names_copy;
}
}
INIT_HLIST_HEAD(&clk->clks);
hw->clk = __clk_create_clk(hw, NULL, NULL);
if (IS_ERR(hw->clk)) {
pr_err("%s: could not allocate per-user clk\n", __func__);
ret = PTR_ERR(hw->clk);
goto fail_parent_names_copy;
}
ret = __clk_init(dev, hw->clk);
if (!ret)
return hw->clk;
__clk_free_clk(hw->clk);
hw->clk = NULL;
fail_parent_names_copy:
while (--i >= 0)
kfree_const(clk->parent_names[i]);
kfree(clk->parent_names);
fail_parent_names:
kfree_const(clk->name);
fail_name:
kfree(clk);
fail_out:
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(clk_register);
上面這段代碼的邏輯比較簡單:
首先, 創建了clk_core結構體, 然后用provider提供的clk_hw, 填充clk_core中的各個字段.
然后, 調用 __clk_create_clk, 在__clk_create_clk里面會創建struct clk這個結構體並初始化其相關字段. clk_register會返回給provider一個struct clk*的結構體指針, 這個clk*就是這里創建的.
下文我們會單獨介紹__clk_create_clk這個函數.
最后, 會調用__clk_init, 在__clk_init里面會處理clk_core之間的樹形結構關系.
下文我們會單獨介紹__clk_init這個函數.
__clk_create_clk
這個API會創建一個struct clk結構體.
要理解此API的細節, 首先得理清楚struct clk和struct clk_core的關系.
我們在4.2節《struct clk_core》中已經初步介紹了clk和clk_core的關系, 可以回頭看看.
這里我們在做進一步的說明.
還記得我們在《字符設備驅動》一文中介紹過的struct file和struct inode這兩個結構體嗎? inode在物理上代表一個文件, 一個文件對應唯一一個inode; file則代表一個打開的文件, 文件被打開幾次, 就會有幾個file.
clk和clk_core的關系與上面很類型, clk_core代表一個硬件上的clock, 一個clock對應唯一一個clk_core; 而clk則代表被使用的clock, 例如GPIO driver想要獲取某個clock, 它會得到一個struct clk, SPI driver想要獲取同一個clock, 它也會得到一個struct clk, 另外, 當此clock的provider向clock子系統注冊時, provider也會得到一個struct clk.
接下來我們看看代碼細節吧:
struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,
const char *con_id)
{
struct clk *clk;
/* This is to allow this function to be chained to others */
if (!hw || IS_ERR(hw))
return (struct clk *) hw;
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
if (!clk)
return ERR_PTR(-ENOMEM);
clk->core = hw->core;
clk->dev_id = dev_id;
clk->con_id = con_id;
clk->max_rate = ULONG_MAX;
clk_prepare_lock();
hlist_add_head(&clk->clks_node, &hw->core->clks);
clk_prepare_unlock();
return clk;
}
邏輯比較簡單:
首先創建struct clk結構體, 然后初始化結構體的相關參數, 最后把這個struct clk掛載到clk_core的clks鏈表頭下面.
__clk_init
這個API主要是處理clock之間的樹形關系.
當任何一個clock調用clk_register接口進行注冊時, __clk_init都會負責把這個clock放在樹形結構的恰當位置.
代碼細節如下:
1: /**
2: * __clk_init - initialize the data structures in a struct clk
3: * @dev: device initializing this clk, placeholder for now
4: * @clk: clk being initialized
5: *
6: * Initializes the lists in struct clk, queries the hardware for the
7: * parent and rate and sets them both.
8: */
9: int __clk_init(struct device *dev, struct clk *clk)
10: {
11: int i, ret = 0;
12: struct clk *orphan;
13: struct hlist_node *tmp2;
14:
15: if (!clk)
16: return -EINVAL;
17:
18: clk_prepare_lock();
19:
20: /* check to see if a clock with this name is already registered */
21: if (__clk_lookup(clk->name)) {
22: pr_debug("%s: clk %s already initialized\n",
23: __func__, clk->name);
24: ret = -EEXIST;
25: goto out;
26: }
27:
28: /* check that clk_ops are sane. See Documentation/clk.txt */
29: if (clk->ops->set_rate &&
30: !(clk->ops->round_rate && clk->ops->recalc_rate)) {
31: pr_warning("%s: %s must implement .round_rate & .recalc_rate\n",
32: __func__, clk->name);
33: ret = -EINVAL;
34: goto out;
35: }
36:
37: if (clk->ops->set_parent && !clk->ops->get_parent) {
38: pr_warning("%s: %s must implement .get_parent & .set_parent\n",
39: __func__, clk->name);
40: ret = -EINVAL;
41: goto out;
42: }
43:
44: /* throw a WARN if any entries in parent_names are NULL */
45: for (i = 0; i < clk->num_parents; i++)
46: WARN(!clk->parent_names[i],
47: "%s: invalid NULL in %s's .parent_names\n",
48: __func__, clk->name);
49:
50: /*
51: * Allocate an array of struct clk *'s to avoid unnecessary string
52: * look-ups of clk's possible parents. This can fail for clocks passed
53: * in to clk_init during early boot; thus any access to clk->parents[]
54: * must always check for a NULL pointer and try to populate it if
55: * necessary.
56: *
57: * If clk->parents is not NULL we skip this entire block. This allows
58: * for clock drivers to statically initialize clk->parents.
59: */
60: if (clk->num_parents > 1 && !clk->parents) {
61: clk->parents = kzalloc((sizeof(struct clk*) * clk->num_parents),
62: GFP_KERNEL);
63: /*
64: * __clk_lookup returns NULL for parents that have not been
65: * clk_init'd; thus any access to clk->parents[] must check
66: * for a NULL pointer. We can always perform lazy lookups for
67: * missing parents later on.
68: */
69: if (clk->parents)
70: for (i = 0; i < clk->num_parents; i++)
71: clk->parents[i] =
72: __clk_lookup(clk->parent_names[i]);
73: }
74:
75: clk->parent = __clk_init_parent(clk);
76:
77: /*
78: * Populate clk->parent if parent has already been __clk_init'd. If
79: * parent has not yet been __clk_init'd then place clk in the orphan
80: * list. If clk has set the CLK_IS_ROOT flag then place it in the root
81: * clk list.
82: *
83: * Every time a new clk is clk_init'd then we walk the list of orphan
84: * clocks and re-parent any that are children of the clock currently
85: * being clk_init'd.
86: */
87: if (clk->parent)
88: hlist_add_head(&clk->child_node,
89: &clk->parent->children);
90: else if (clk->flags & CLK_IS_ROOT)
91: hlist_add_head(&clk->child_node, &clk_root_list);
92: else
93: hlist_add_head(&clk->child_node, &clk_orphan_list);
94:
95: /*
96: * Set clk's rate. The preferred method is to use .recalc_rate. For
97: * simple clocks and lazy developers the default fallback is to use the
98: * parent's rate. If a clock doesn't have a parent (or is orphaned)
99: * then rate is set to zero.
100: */
101: if (clk->ops->recalc_rate)
102: clk->rate = clk->ops->recalc_rate(clk->hw,
103: __clk_get_rate(clk->parent));
104: else if (clk->parent)
105: clk->rate = clk->parent->rate;
106: else
107: clk->rate = 0;
108:
109: /*
110: * walk the list of orphan clocks and reparent any that are children of
111: * this clock
112: */
113: hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
114: if (orphan->ops->get_parent) {
115: i = orphan->ops->get_parent(orphan->hw);
116: if (!strcmp(clk->name, orphan->parent_names[i]))
117: __clk_reparent(orphan, clk);
118: continue;
119: }
120:
121: for (i = 0; i < orphan->num_parents; i++)
122: if (!strcmp(clk->name, orphan->parent_names[i])) {
123: __clk_reparent(orphan, clk);
124: break;
125: }
126: }
127:
128: /*
129: * optional platform-specific magic
130: *
131: * The .init callback is not used by any of the basic clock types, but
132: * exists for weird hardware that must perform initialization magic.
133: * Please consider other ways of solving initialization problems before
134: * using this callback, as it's use is discouraged.
135: */
136: if (clk->ops->init)
137: clk->ops->init(clk->hw);
138:
139: clk_debug_register(clk);
140:
141: out:
142: clk_prepare_unlock();
143:
144: return ret;
145: }
這一段代碼的邏輯很復雜, 主要做的事情如下:
20~26行,以clock name為參數,調用__clk_lookup接口,查找是否已有相同name的clock注冊,如果有,則返回錯誤。由此可以看出,clock framework以name唯一識別一個clock,因此不能有同名的clock存在
28~42行,檢查clk ops的完整性,例如:如果提供了set_rate接口,就必須提供round_rate和recalc_rate接口;如果提供了set_parent,就必須提供get_parent
50~73行,分配一個struct clk *類型的數組,緩存該clock的parents clock。具體方法是根據parents_name,查找相應的struct clk指針
75行,獲取當前的parent clock,並將其保存在parent指針中。具體可參考下面“說明2”
77~93行,根據該clock的特性,將它添加到clk_root_list、clk_orphan_list或者parent->children三個鏈表中的一個,具體請參考下面“說明1”
95~107行,計算clock的初始rate,具體請參考下面“說明3”
109~126行,嘗試reparent當前所有的孤兒(orphan)clock,具體請參考下面“說明4”
128~137行,如果clock ops提供了init接口,執行之(由注釋可知,kernel不建議提供init接口)
說明1: clock的管理和查詢
clock framework有2條全局的鏈表:clk_root_list和clk_orphan_list。所有設置了CLK_IS_ROOT屬性的clock都會掛在clk_root_list中。其它clock,如果有valid的parent ,則會掛到parent的“children”鏈表中,如果沒有valid的parent,則會掛到clk_orphan_list中。
查詢時(__clk_lookup接口做的事情),依次搜索:
clk_root_list-->root_clk-->children-->child's children
clk_orphan_list-->orphan_clk-->children-->child's children
即可
說明2:當前parent clock的選擇(__clk_init_parent)
對於沒有parent,或者只有1個parent 的clock來說,比較簡單,設置為NULL,或者根據parent name獲得parent的struct clk指針接。
對於有多個parent的clock,就必須提供.get_parent ops,該ops要根據當前硬件的配置情況,例如寄存器值,返回當前所有使用的parent的index(即第幾個parent)。然后根據index,取出對應parent clock的struct clk指針,作為當前的parent。
說明3: clock的初始rate計算
對於提供.recalc_rate ops的clock來說,優先使用該ops獲取初始的rate。如果沒有提供,退而求其次,直接使用parent clock的rate。最后,如果該clock沒有parent,則初始的rate只能選擇為0
.recalc_rate ops的功能,是以parent clock的rate為輸入參數,根據當前硬件的配置情況,如寄存器值,計算獲得自身的rate值
說明4:orphan clocks的reparent
有些情況下,child clock會先於parent clock注冊,此時該child就會成為orphan clock,被收養在clk_orphan_list中。
而每當新的clock注冊時,kernel都會檢查這個clock是否是某個orphan的parent,如果是,就把這個orphan從clk_orphan_list中移除,放到新注冊的clock的懷抱。這就是reparent的功能,它的處理邏輯是:
遍歷orphan list,如果orphan提供了.get_parent ops,則通過該ops得到當前parent的index,並從parent_names中取出該parent的name,然后和新注冊的clock name比較,如果相同,呵呵,找到parent了,執行__clk_reparent,進行后續的操作
如果沒有提供.get_parent ops,只能遍歷自己的parent_names,檢查是否有和新注冊clock匹配的,如果有,執行__clk_reparent,進行后續的操作
__clk_reparent會把這個orphan從clk_orphan_list中移除,並掛到新注冊的clock上。然后調用__clk_recalc_rates,重新計算自己以及自己所有children的rate。計算過程和上面的clock rate設置類似
4. clock consumer -- 如何使用Clocks
5.1 簡介
clock 子系統管理clocks的最終目的, 是讓device driver可以方便的獲取並使用這些clocks.
我們知道clock子系統用一個struct clk結構體來抽象某一個clock.
當device driver要操作某個clock時, 它需要做兩件事情:
首先, 獲取clock. 也叫clk_get.
然后, 操作這個clock. 如 clk_prepare/ clk_enable/ clk_disable/ clk_set_rate/ …
舉個例子, 以GPIO控制器為例. 從硬件的角度來說, GPIO控制器需要工作時鍾. 因此, 在GPIO控制器的platform_driver的probe函數里面, 就需要操作這個時鍾.
操作的第一步是獲取clock, 獲取clock有兩種方式:
第一種方式是直接通過name來獲取, 例如假設已知GPIO控制器的工作時鍾的name是”gpio_clk”, 那么我們就可以在probe函數里面通過clk_get(&device, “gpio_clk”) 這種方式獲取.
另外一種方式是說, 對於GPIO控制器來講, 我們會有一個platform_device, 在里面描述GPIO控制器的資源, 也就是寄存器地址, 中斷號什么的. GPIO控制器的工作時鍾也算一種資源了, 我們能否在platform_device里面一並描述這個資源呢?
答案是可以. platform_device現在都是在DTS中描述的, 因此這個GPIO控制器的DTS可以這樣寫:
gpio : gpio-controller@xxxx {
compatible = “yyyy”;
reg = <…. ….>;
……
clocks = <&theclock>; /* 指明/引用某一個clock */
}
我們用clocks這個property來描述時鍾資源.
在對應的platform_driver的probe函數里面, 我們就可以通過clk_get(&device, NULL)這種方式來獲取所需的clock.
這種方式下, 我們只需要知道platform_device, 然后就能通過它找到對應的DTS node, 然后就能通過DTS node的”clocks” property獲取到對應的時鍾資源.
獲取到clk之后, 就可以通過clock子系統提供的API來操作clk了.
clock子系統提供給consumer端的APIs都定義在頭文件 include/linux/clk.h里面.
接下來的章節, 我們會分兩部分來介紹這些APIs.
5.2節介紹獲取clk相關的APIs.
5.3節介紹操作clk相關的APIs.
5.2 APIs – 獲取clk
頭文件: include/linux/clk.h
實現文件: drivers/clk/clkdev.c
最主要的兩個API:
struct clk *clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get(struct device *dev, const char *id);
devm_clk_get是devm版本的clk_get. 用於自動釋放資源, 我們在《設備模型》一文中有過介紹, 這里不多說了.
除了這兩個用的最多的API之外, 還有一些其他的API, 如下:
struct clk *clk_get_sys(const char *dev_id, const char *con_id)
struct clk *of_clk_get(struct device_node *np, int index);
struct clk *of_clk_get_by_name(struct device_node *np, const char *name);
struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);
clk_get相當於一個總邏輯, 它會根據不同的情況調用上述這些API, 在實際代碼中, 基本上我們只會用到clk_get或者devm_clk_get.
接下來, 我們會仔細看看clk_get的內部邏輯.
clk_get / devm_clk_get
頭文件: include/linux/clk.h
實現文件: drivers/clk/clkdev.c
原型: struct clk *clk_get(struct device *dev, const char *id);
該API的主要作用就是根據參數, 從clock子系統中獲取一個clk給到consumer, 然后consumer就可以操作該clk了. 因此該API的返回值就是用於描述某個clock的數據結構: struct clk.
代碼細節如下:
struct clk *clk_get(struct device *dev, const char *con_id)
{
const char *dev_id = dev ? dev_name(dev) : NULL;
struct clk *clk;
if (dev) {
clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);
if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
return clk;
}
return clk_get_sys(dev_id, con_id);
}
EXPORT_SYMBOL(clk_get);
如果你已經充分理解了2.3節的那個系統框圖, 那么此處的邏輯就很簡單了:
如果dev不為空, 那么就以dev->of_node為參數, 調用of_XXX那一套, 從LIST_HEAD(of_clk_providers)這個池子里面查詢某個clk.
如果查詢到了, 則返回該clk. 這種情況其實對應5.1節中描述的DTS node方式.
如果上面沒有獲取到clk, 則調用clk_get_sys, 從LIST_HEAD(clocks)這個池子里面查詢clk. 查詢的關鍵字是con_id, 其實就是clock的name.
5.3 APIs – 操作clk
頭文件: include/linux/clk.h
實現文件: drivers/clk/clk.c
1: int clk_prepare(struct clk *clk)
2: void clk_unprepare(struct clk *clk)
3:
4: static inline int clk_enable(struct clk *clk)
5: static inline void clk_disable(struct clk *clk)
6:
7: static inline unsigned long clk_get_rate(struct clk *clk)
8: static inline int clk_set_rate(struct clk *clk, unsigned long rate)
9: static inline long clk_round_rate(struct clk *clk, unsigned long rate)
10:
11: static inline int clk_set_parent(struct clk *clk, struct clk *parent)
12: static inline struct clk *clk_get_parent(struct clk *clk)
13:
14: static inline int clk_prepare_enable(struct clk *clk)
15: static inline void clk_disable_unprepare(struct clk *clk)
clk_enable/clk_disable: 啟動/停止clock. 不會睡眠
clk_prepare/clk_unprepare: 啟動clock前的准備工作/停止clock后的善后工作. 可能會睡眠
clk_get_rate/clk_set_rate/clk_round_rate: clock頻率的獲取和設置, 其中clk_set_rate可能會不成功(例如沒有對應的分頻比), 此時會返回錯誤. 如果要確保設置成功, 則需要先調用clk_round_rate接口, 得到和需要設置的rate比較接近的那個值
clk_set_parent / clk_get_parent: 獲取/選擇clock的parent clock
clk_prepare_enable: 將clk_prepare和clk_enable組合起來,一起調用
clk_disable_unprepare: 將clk_disable和clk_unprepare組合起來,一起調用
prepare/unprepare,enable/disable的說明:
這兩套API的本質,是把clock的啟動/停止分為atomic和non-atomic兩個階段,以方便實現和調用。
因此上面所說的“不會睡眠/可能會睡眠”,有兩個角度的含義:
一是告訴底層的clock driver,請把可能引起睡眠的操作,放到prepare/unprepare中實現,一定不能放到enable/disable中
二是提醒上層使用clock的driver,調用prepare/unprepare接口時可能會睡眠哦,千萬不能在atomic上下文(例如中斷處理中)調用哦,而調用enable/disable接口則可放心。
另外,clock的開關為什么需要睡眠呢?這里舉個例子,例如enable PLL clk,在啟動PLL后,需要等待它穩定。而PLL的穩定時間是很長的,這段時間要把CPU交出(進程睡眠),不然就會浪費CPU。
最后,為什么會有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果調用者能確保是在non-atomic上下文中調用,就可以順序調用prepare/enable、disable/unprepared,為了簡單,framework就幫忙封裝了這兩個接口。
5.4 其它APIs
頭文件: include/linux/clk.h
實現文件: drivers/clk/clk.c
1: int clk_notifier_register(struct clk *clk, struct notifier_block *nb);
2: int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);
這兩個API與內核提供的通知鏈機制有關.
這兩個notify接口, 用於注冊/注銷 clock rate改變的通知.
例如某個driver關心某個clock, 期望這個clock的rate改變時, 通知到自己, 就可以注冊一個notify.