通用時鐘框架

作者:

Mike Turquette <mturquette@ti.com>

本文件旨在解釋通用時鐘框架的細節,以及如何將平臺移植到此框架。它還不是對 include/linux/clk.h 中時鐘 api 的詳細解釋,但也許有一天它會包含該資訊。

簡介和介面拆分

通用時鐘框架是控制當今各種裝置上可用時鐘節點的介面。這可能以時鐘門控、速率調整、多路複用或其他操作的形式出現。 此框架透過 CONFIG_COMMON_CLK 選項啟用。

介面本身分為兩半,每一半都遮蔽了另一半的細節。 首先是 struct clk 的通用定義,它統一了框架級別的記賬和基礎設施,這些記賬和基礎設施傳統上在各種平臺中重複。 第二個是 clk.h api 的通用實現,在 drivers/clk/clk.c 中定義。 最後是 struct clk_ops,其操作由 clk api 實現呼叫。

介面的後半部分由使用 struct clk_ops 註冊的特定於硬體的回撥以及對特定時鐘建模所需的相應特定於硬體的結構組成。 對於本文件的其餘部分,對 struct clk_ops 中回撥的任何引用,例如 .enable 或 .set_rate,都意味著該程式碼的特定於硬體的實現。 同樣,對 struct clk_foo 的引用可以方便地簡寫為假設的“foo”硬體的特定於硬體位的實現。

將此介面的兩半聯絡在一起的是 struct clk_hw,它在 struct clk_foo 中定義,並在 struct clk_core 中指向。 這允許在公共時鐘介面的兩個離散半部分之間輕鬆導航。

通用資料結構和 api

下面是 drivers/clk/clk.c 中的通用 struct clk_core 定義,為簡潔起見進行了修改

struct clk_core {
        const char              *name;
        const struct clk_ops    *ops;
        struct clk_hw           *hw;
        struct module           *owner;
        struct clk_core         *parent;
        const char              **parent_names;
        struct clk_core         **parents;
        u8                      num_parents;
        u8                      new_parent_index;
        ...
};

上面的成員構成了 clk 樹拓撲的核心。 clk api 本身定義了幾個面向驅動程式的函式,這些函式在 struct clk 上執行。 該 api 記錄在 include/linux/clk.h 中。

利用通用 struct clk_core 的平臺和裝置使用 struct clk_core 中的 struct clk_ops 指標來執行 clk-provider.h 中定義的操作的特定於硬體的部分

struct clk_ops {
        int             (*prepare)(struct clk_hw *hw);
        void            (*unprepare)(struct clk_hw *hw);
        int             (*is_prepared)(struct clk_hw *hw);
        void            (*unprepare_unused)(struct clk_hw *hw);
        int             (*enable)(struct clk_hw *hw);
        void            (*disable)(struct clk_hw *hw);
        int             (*is_enabled)(struct clk_hw *hw);
        void            (*disable_unused)(struct clk_hw *hw);
        unsigned long   (*recalc_rate)(struct clk_hw *hw,
                                        unsigned long parent_rate);
        long            (*round_rate)(struct clk_hw *hw,
                                        unsigned long rate,
                                        unsigned long *parent_rate);
        int             (*determine_rate)(struct clk_hw *hw,
                                          struct clk_rate_request *req);
        int             (*set_parent)(struct clk_hw *hw, u8 index);
        u8              (*get_parent)(struct clk_hw *hw);
        int             (*set_rate)(struct clk_hw *hw,
                                    unsigned long rate,
                                    unsigned long parent_rate);
        int             (*set_rate_and_parent)(struct clk_hw *hw,
                                    unsigned long rate,
                                    unsigned long parent_rate,
                                    u8 index);
        unsigned long   (*recalc_accuracy)(struct clk_hw *hw,
                                        unsigned long parent_accuracy);
        int             (*get_phase)(struct clk_hw *hw);
        int             (*set_phase)(struct clk_hw *hw, int degrees);
        void            (*init)(struct clk_hw *hw);
        void            (*debug_init)(struct clk_hw *hw,
                                      struct dentry *dentry);
};

硬體 clk 實現

通用 struct clk_core 的優勢來自於它的 .ops 和 .hw 指標,它們將 struct clk 的細節從特定於硬體的位中抽象出來,反之亦然。 為了說明這一點,請考慮 drivers/clk/clk-gate.c 中簡單的可門控 clk 實現

struct clk_gate {
        struct clk_hw   hw;
        void __iomem    *reg;
        u8              bit_idx;
        ...
};

struct clk_gate 包含 struct clk_hw hw 以及關於哪個暫存器和位控制此 clk 門控的特定於硬體的知識。 這裡不需要任何關於時鐘拓撲或記帳的資訊,例如 enable_count 或 notifier_count。 這一切都由通用框架程式碼和 struct clk_core 處理。

讓我們從驅動程式程式碼中逐步啟用此 clk

struct clk *clk;
clk = clk_get(NULL, "my_gateable_clk");

clk_prepare(clk);
clk_enable(clk);

clk_enable 的呼叫圖非常簡單

clk_enable(clk);
        clk->ops->enable(clk->hw);
        [resolves to...]
                clk_gate_enable(hw);
                [resolves struct clk gate with to_clk_gate(hw)]
                        clk_gate_set_bit(gate);

以及 clk_gate_set_bit 的定義

static void clk_gate_set_bit(struct clk_gate *gate)
{
        u32 reg;

        reg = __raw_readl(gate->reg);
        reg |= BIT(gate->bit_idx);
        writel(reg, gate->reg);
}

請注意,to_clk_gate 定義為

#define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw)

這種抽象模式用於每個時鐘硬體表示。

支援您自己的 clk 硬體

在實現對新型時鐘的支援時,只需包含以下標頭

#include <linux/clk-provider.h>

要為您的平臺構建 clk 硬體結構,您必須定義以下內容

struct clk_foo {
        struct clk_hw hw;
        ... hardware specific data goes here ...
};

要利用您的資料,您需要支援對您的 clk 的有效操作

struct clk_ops clk_foo_ops = {
        .enable         = &clk_foo_enable,
        .disable        = &clk_foo_disable,
};

使用 container_of 實現上述函式

#define to_clk_foo(_hw) container_of(_hw, struct clk_foo, hw)

int clk_foo_enable(struct clk_hw *hw)
{
        struct clk_foo *foo;

        foo = to_clk_foo(hw);

        ... perform magic on foo ...

        return 0;
};

下面是一個矩陣,詳細說明了哪些 clk_ops 是強制性的,具體取決於該時鐘的硬體功能。 標記為“y”的單元格表示強制性的,標記為“n”的單元格表示包含該回調無效或不必要。 空單元格是可選的,或者必須根據具體情況進行評估。

時鐘硬體特性

更改速率

單個父時鐘

多路複用器

.prepare

.unprepare

.enable

y

.disable

y

.is_enabled

y

.recalc_rate

y

.round_rate

y [1]

.determine_rate

y [1]

.set_rate

y

.set_parent

n

y

n

.get_parent

n

y

n

.recalc_accuracy

.init

最後,使用特定於硬體的註冊函式在執行時註冊您的時鐘。 此函式只需填充 struct clk_foo 的資料,然後透過呼叫將通用 struct clk 引數傳遞給框架

clk_register(...)

有關示例,請參見 drivers/clk/clk-*.c 中的基本時鐘型別。

停用未使用時鐘的時鐘門控

有時在開發過程中,能夠繞過預設停用未使用時鐘的功能會很有用。 例如,如果驅動程式沒有正確啟用時鐘,而是依賴於啟動載入程式啟動時鐘,則繞過停用意味著驅動程式將保持功能,同時對問題進行排序。

您可以使用以下引數啟動核心來檢視哪些時鐘已被停用

tp_printk trace_event=clk:clk_disable

要繞過此停用,請在核心的 bootargs 中包含“clk_ignore_unused”。

鎖定

通用時鐘框架使用兩個全域性鎖,prepare 鎖和 enable 鎖。

enable 鎖是一個自旋鎖,在對 .enable、.disable 操作的呼叫期間一直持有。 因此,不允許這些操作休眠,並且允許在原子上下文中呼叫 clk_enable()clk_disable() API 函式。

對於 clk_is_enabled() API,它也被設計為允許在原子上下文中使用。 但是,除非你想用該鎖中的啟用狀態資訊做其他事情,否則在核心中持有啟用鎖沒有任何意義。 否則,檢視 clk 是否已啟用是對啟用狀態的單次讀取,該狀態可能會在函式返回後輕易更改,因為該鎖已釋放。 因此,此 API 的使用者需要處理啟用狀態的讀取與其使用的任何內容之間的同步,以確保啟用狀態在此期間不會更改。

prepare 鎖是一個互斥鎖,在對所有其他操作的呼叫期間一直持有。 允許所有這些操作休眠,並且不允許在原子上下文中呼叫相應的 API 函式。

這實際上從鎖定的角度將操作分為兩組。

驅動程式不需要手動保護在一個組的操作之間共享的資源,無論這些資源是否由多個時鐘共享。 但是,對兩個組的操作之間共享的資源的訪問需要由驅動程式保護。 這樣的資源的一個示例是控制時鐘速率和時鐘啟用/停用狀態的暫存器。

時鐘框架是可重入的,因為允許驅動程式從其時鐘操作的實現中呼叫時鐘框架函式。 例如,這可能會導致一個時鐘的 .set_rate 操作從另一個時鐘的 .set_rate 操作中呼叫。 在驅動程式實現中必須考慮這種情況,但在這種情況下,程式碼流通常由驅動程式控制。

請注意,當通用時鐘框架之外的程式碼需要訪問時鐘操作使用的資源時,也必須考慮鎖定。 這被認為超出了本文件的範圍。