Clock子系統


1.   架構介紹

Clock統是Linux內核中專門管理時鍾的子系統.

時鍾在嵌入式系統中很重要, 它就像人的脈搏一樣, 驅動器件工作.

任何一個CPU, 都需要給它提供一個外部晶, 這個晶振就是用來提供時鍾的; 任何一個CPU內部的片上外設, 也需要工作時鍾: 例如GPIO控制器, 首先得給它提供工作時鍾, 然后才能訪問它的寄存器.

 

如果你去看一個ARM CPU的芯片手冊, 你一定能找到一個章節, 專門描述系統時鍾, 一般稱之為時鍾樹(clock tree).

芯片手冊從硬件的角度上描述了某個CPU的時鍾系統是如何設計的, Clock子系統從軟件的層面來抽象這個設計.

 

在本章中, 我們首先從硬件的角度來看看一個時鍾樹的例子, 然后自己思考一下軟件層面該如何設計, 最后看看clock子系統是怎么做的.

2.1             時鍾樹

如今, 可運行Linux的主流CPU, 都有非常復雜的clock tree, 我們隨便拿一個處理器的spec, 查看clock相關的章節, 一定會有一個非常龐大和復雜的樹狀圖. 這個圖由clock相關的器件,以及這些器件輸出的clock組成.

 

下圖是一個簡單的示例:

clock相關的器件包括

         用於產生clockOscillator(有源振盪器, 也稱作振盪器)或者Crystal(無源振盪器, 也稱晶振)

         用於倍頻的PLL(鎖相環, Phase Locked Loop

         用於分頻的divider

         用於多路選擇的Mux

         用於clock enable控制的與門

         使用clock的硬件模塊(可稱作consumer, 例如HW1, 它可能是GPIO控制器

 

器件用於產生具體的clock, 例如osc_clk. 這些器件的特性如下:

         從輸入(parent)和輸出(children)的角度來看

         某些器件沒有parent, 有一個或多個輸出. 例如osc_clk

         某些器件有一個parent, 有一個或多個輸出. 例如PLL1, 它的parentosc_clk, 輸出只有一個pll1_clk

         某些器件有多個parent, 有一個或多個輸出. 例如MUX, 它有多個parent, 輸出只有一個hw3_clk

         從頻率的角度上來看

         某些clock的頻率是可以調整的, 我們可以通過設置倍頻和分頻因子來調整輸出頻率.

這一類clock最常見.

         某些clock的頻率是固定的, 而且不能開關, 比如osc_clk. 最常見的是24M25M.

這一類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             軟件抽象

上一節我們介紹了時鍾樹, 並介紹了時鍾的providerconsumer. 一個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其實就是編寫PCMdriver.

PCM也是CPU的一個片上外設, 因此它也會借用platform這套機制. 因此我們就需要有platform_device來描述設備, 同時要有與之對應的platform_driver來控制設備. 所謂控制, 就寫讀寫PCM的寄存器來使能/關閉時鍾, 設置時鍾頻率等等. platform_driverprobe函數中, 還有一項重要功能, 就是調用clock子系統提供的API, clock子系統注冊.

 

下面, 我看看編寫clock driver 的大致步驟是怎樣的.

3.2             編寫clock driver的大致步驟

由前文可知, 首先你得准備一個platform_device, 引入device tree的機制后, platform_devicedts替代了, 因此我們就需要在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, 對比一下, 是不是很類似.

從這個例子里面, 我們可以看出一個clockDTS node的基本語法:

         compatible , 決定了與這個node匹配的driver

         reg就是用來描述PCM的寄存器

         #clock-cells, 這個是clock provider node獨有的, 表明在引用此clock, 需要用幾個32位來描述. 什么意思呢?

 

假設這個clock只有一個輸出時鍾, 那么#clock-cells = <0>, 我們在引用此clock的時候, 只用指明此clock即可.

引用此clock是什么意思? 引用指的是clockconsumer. 例如GPIO模塊需要工作時鍾, 那么我們在編寫GPIODTS node, 需要指明它的工作時鍾是多少, 這個過程就是引用, 寫個簡單的例子:

gpio : gpio-controller@xxxx {

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 nodelable, 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: 

  26:     osc32k: osc32k {

  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類型的指針, Linuxclock子系統中, 用一個struct clk代表一個clock. 你的CPU的時鍾樹里有多少個clock, 就會有多少個對應的struct clk.

 

當你拿到返回結果之后, 你需要調用另外一個API : of_clk_add_provider, 把剛剛拿到的返回結果通過此API丟到池子里面, 好讓consumer從這個池子里獲取某一個clock.

 

池子的概念我們在前文講述過, 除了上述方法, 你還可以用另外一種方法把struct clk添加到池子里面.

如果你要用這種方法, 你會用到clock子系統提供給你的另外幾個API, 這些API的定義在: include/linux/clkdev.h

其中最主要的一個APIint clk_register_clkdev(struct clk *, const char *, const char *, ...), 你可以在你的clock driver里面調用這個API, 剛剛拿到的返回結果通過此API丟到池子里面.

 

為什么會存在這兩種方式呢? 得從consumer的角度來解答這個問題.

我們用GPIO來舉個例子, GPIO控制器需要工作時鍾, 這個時鍾假設叫gpio_clk, 它是一個provider. 你需要把這個provider注冊進clock子系統, 並把用於描述這個gpio_clkstruct clk添加到池子里面.

GPIO控制器的driver代碼里, 我們需要獲取到gpio_clk這個時鍾並使能它, 獲取的過程就是向池子查詢.

怎么查詢? 你可以直接給定一個name, 然后通過這個name向池子查詢; 你也可以在GPIODTS  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_datastruct clk_ops.

下面我們看看這幾個數據結構.

struct clk_hw

頭文件: include/linux/clk-provider.h

struct  clk_hw

Comment

struct clk_core *core

clk_coreclock子系統核心層的一個數據結構, 由核心層代碼創建和維護, 一個clk_core對應一個具體的clock.

struct clk *clk

clk也是clock子系統核心層的一個數據結構, 同樣由核心層代碼創建和維護, 它也對應一個具體的clock.

clk_coreclk的細節, 放在《clock core》一章中描述

const struct clk_init_data *init

clk_init_dataprovider需要實現並填充的一個數據結構, 編寫clock driver, 最主要的任務就是實現它

struct clk_init_data

頭文件: include/linux/clk-provider.h

struct  clk_init_data

Comment

const char*name

clockname, 系統中會存在很多個clock, 每一個clock都會有一個名稱, 可以用名稱來區分不同的clock, 因此不能重名

const struct clk_ops*ops

clock控制相關的ops, 例如enable/disable; set_rate等等.

consumer端想要操作某個clock, 最終就會調用到該clockclk_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個函數, prepareenable.

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的同時, 會通過一個指針, 返回roundparentrate. 這和CLK_SET_RATE_PARENT flag有關

 

clock consumer調用clk_round_rate獲取一個近似的rate, 如果該clock沒有提供.round_rate函數, 有兩種方法:

         在沒有設置CLK_SET_RATE_PARENT標志時, 直接返回該clockcache 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_registerclock子系統提供的注冊clock基礎的API函數, 后文描述的其它APIs都是對它的封裝.

devm_clk_registerclk_registerdevm版本, 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_*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, 它的參數列表如下:

         nameclock的名稱

         parent_nameparent 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 clockDTS處理方式.

如果你想借用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_*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_flagsdivider 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_*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_parentsparent clock的個數

         regshiftwidth選擇parent的寄存器、偏移、寬度

         clk_mux_flagsmux 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(即multiplierdividerclock的頻率是由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子系統還提供了此種類型clockDTS接口.

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_*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);

 

clock子系統沒有提供相關的DTS解析函數.

clk_register_composite

顧名思義, 就是muxdividergateclock的組合, 可通過下面接口注冊:

 

頭文件: 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~5clock, 這里就只剩下苦力了, 耐心一點,就可以了.

 

另外, 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子系統還提供了此種類型clockDTS接口.

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, 並返回一個代表該clockstruct 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.

通過引用的這個clockphandle, 就能找到對應的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_coreclock核心層的一個私有數據結構, 下面細述

const char *dev_id

使用該clkdevicename

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, I2C3個模塊都是用此pll_clk做為工作時鍾的.

那么clock子系統核心層會有一個clk_core用於描述pll_clk, 每當GPIO/SPI/I2Cdriver首次嘗試獲取pll_clk, clock子系統就會創建一個struct clk結構體, 並將創建的這個struct clk返回給對應的driver. clk結構體的dev_idcon_id都是對應的driver指定的.

 

不過為了簡便起見, 我們在本文中還是暫定一個struct clk對應一個clock, 理解起來不會有什么異常.

struct clk_core

前文我們說過, 一個struct clk就代表一個clock. 一個clk_coreclock也是一一對應的關系. 那它倆有什么區別呢?

 

struct clk更像是對外的接口, 例如當providerclock子系統注冊時, 它會得到一個struct clk*的返回結果; consumer想要使用某個clock, 也會首先獲取struct clk*這個結構體.

 

struct clk_core則是clock子系統核心層的一個私有數據結構, 核心層代描述某個具體的clock. providerconsumer不會觸碰這個數據結構.

 

結構體定義在一個C文件里面 : drivers/clk/clk.c

struct  clk_core

Comment

const char*name

clockname,  provider在注冊的時候會提供clockname

const struct clk_ops*ops

操作此clockops,  provider在注冊的時候會提供clockops

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

clockrate

unsigned longreq_rate

consumer端要求rate

unsigned longnew_rate

新的rate

struct clk_core*new_parent

新的parent

struct clk_core*new_child

新的child

unsigned longflags

clockflags,  可選的flag3.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掛接到對應的parentchildren鏈表下

struct hlist_nodedebug_node

鏈表節點, debugfs相關

struct hlist_headclks

一個struct clk_core可能對應多個struct clk, 這個鏈表頭掛接所有的clks.

關於struct clk_corestruct 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 clkstruct clk_core這兩個數據結構.

然后, 它得維護clk_core的樹形關系, 就像時鍾數的硬件形態那樣:

有一個或多個ROOT_CLOCK, ROOT_CLOCK沒有parent, 下面掛載的是children, children下面在掛載children.

為什么要維護這樣的樹形結構呢? 因為我們在操作某個clk, 往往會跟它的parent有關: 例如要使能某個clk, 必須保證它的parent也使能; 或者parentrate改變了, 那它的childrenrate都有可能改變. 因此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 clkstruct clk_core的關系.

 

我們在4.2節《struct clk_core》中已經初步介紹了clkclk_core的關系, 可以回頭看看.

這里我們在做進一步的說明.

還記得我們在《字符設備驅動》一文中介紹過的struct filestruct inode這兩個結構體嗎? inode在物理上代表一個文件, 一個文件對應唯一一個inode; file則代表一個打開的文件, 文件被打開幾次, 就會有幾個file.

clkclk_core的關系與上面類型, clk_core代表一個硬件上的clock, 一個clock對應唯一一個clk_core; clk則代表被使用的clock, 例如GPIO driver想要獲取某個clock, 它會得到一個struct clk, SPI driver想要獲取同一個clock, 它也會得到一個struct clk, 另外, 當此clockproviderclock子系統注冊時, 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_coreclks鏈表頭下面.

__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接口,查找是否已有相同nameclock注冊,如果有,則返回錯誤。由此可以看出,clock frameworkname唯一識別一個clock,因此不能有同名的clock存在

         28~42行,檢查clk ops的完整性,例如:如果提供了set_rate接口,就必須提供round_raterecalc_rate接口;如果提供了set_parent,就必須提供get_parent

         50~73行,分配一個struct clk *類型的數組,緩存該clockparents clock。具體方法是根據parents_name,查找相應的struct clk指針

         75行,獲取當前的parent clock,並將其保存在parent指針中。具體可參考下面“說明2

         77~93行,根據該clock的特性,將它添加到clk_root_listclk_orphan_list或者parent->children三個鏈表中的一個,具體請參考下面“說明1

         95~107行,計算clock的初始rate,具體請參考下面“說明3

         109~126行,嘗試reparent當前所有的孤兒(orphanclock,具體請參考下面“說明4

         128~137行,如果clock ops提供了init接口,執行之(由注釋可知,kernel不建議提供init接口)

 

說明1: clock的管理和查詢

clock framework2條全局的鏈表:clk_root_listclk_orphan_list。所有設置了CLK_IS_ROOT屬性的clock都會掛在clk_root_list中。其它clock,如果有validparent ,則會掛到parent的“children”鏈表中,如果沒有validparent,則會掛到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,或者只有1parent clock來說,比較簡單,設置為NULL,或者根據parent name獲得parentstruct clk指針接。

對於有多個parentclock,就必須提供.get_parent ops,該ops要根據當前硬件的配置情況,例如寄存器值,返回當前所有使用的parentindex(即第幾個parent)。然后根據index,取出對應parent clockstruct clk指針,作為當前的parent

 

說明3: clock的初始rate計算

對於提供.recalc_rate opsclock來說,優先使用該ops獲取初始的rate。如果沒有提供,退而求其次,直接使用parent clockrate。最后,如果該clock沒有parent,則初始的rate只能選擇為0

.recalc_rate ops的功能,是以parent clockrate為輸入參數,根據當前硬件的配置情況,如寄存器值,計算獲得自身的rate

 

說明4orphan clocksreparent

有些情況下,child clock會先於parent clock注冊,此時該child就會成為orphan clock,被收養在clk_orphan_list中。

而每當新的clock注冊時,kernel都會檢查這個clock是否是某個orphanparent,如果是,就把這個orphanclk_orphan_list中移除,放到新注冊的clock的懷抱。這就是reparent的功能,它的處理邏輯是:

         遍歷orphan list,如果orphan提供了.get_parent ops,則通過該ops得到當前parentindex,並從parent_names中取出該parentname,然后和新注冊的clock name比較,如果相同,呵呵,找到parent了,執行__clk_reparent,進行后續的操作

         如果沒有提供.get_parent ops,只能遍歷自己的parent_names,檢查是否有和新注冊clock匹配的,如果有,執行__clk_reparent,進行后續的操作

         __clk_reparent會把這個orphanclk_orphan_list中移除,並掛到新注冊的clock上。然后調用__clk_recalc_rates,重新計算自己以及自己所有childrenrate。計算過程和上面的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_driverprobe函數里面, 就需要操作這個時鍾.

 

操作的第一步是獲取clock, 獲取clock有兩種方式:

 

第一種方式是直接通過name來獲取, 例如假設已知GPIO控制器的工作時鍾的namegpio_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_driverprobe函數里面, 我們就可以通過clk_get(&device, NULL)這種方式來獲取所需的clock.

這種方式下, 我們只需要知道platform_device, 然后就能通過它找到對應的DTS node, 然后就能通過DTS  nodeclocks 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_getdevm版本的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, 其實就是clockname.

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: 獲取/選擇clockparent clock

         clk_prepare_enableclk_prepareclk_enable組合起來,一起調用

         clk_disable_unprepare: clk_disableclk_unprepare組合起來,一起調用

 

prepare/unprepareenable/disable的說明:

這兩套API的本質,是把clock的啟動/停止分為atomicnon-atomic兩個階段,以方便實現和調用。

因此上面所說的“不會睡眠/可能會睡眠”,有兩個角度的含義:

一是告訴底層的clock driver,請把可能引起睡眠的操作,放到prepare/unprepare中實現,一定不能放到enable/disable

二是提醒上層使用clockdriver,調用prepare/unprepare接口時可能會睡眠哦,千萬不能在atomic上下文(例如中斷處理中)調用哦,而調用enable/disable接口則可放心。

 

另外,clock的開關為什么需要睡眠呢?這里舉個例子,例如enable PLL clk,在啟動PLL后,需要等待它穩定。而PLL的穩定時間是很長的,這段時間要把CPU交出(進程睡眠),不然就會浪費CPU

 

最后,為什么會有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果調用者能確保是在non-atomic上下文中調用,就可以順序調用prepare/enabledisable/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, 期望這個clockrate改變時, 通知到自己, 就可以注冊一個notify.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM