1. 程式人生 > >Linux下時鐘框架實踐---一款芯片的時鐘樹配置

Linux下時鐘框架實踐---一款芯片的時鐘樹配置

sys cpu unsigned spi 如何實現 良好的 src 偏移 根據

關鍵詞:時鐘、PLL、Mux、Divider、Gate、clk_summary等。

時鐘和電源是各種設備的基礎設施,整個時鐘框架可以抽象為幾種基本的元器件:負責提供晶振

Linux內核提供了良好的CCF(Common Clock Framework),框架的兩端一個是provider,一個是consumer。

provider指的是提供時鐘模塊,包括晶振、PLL、Mux、Divider、Gate等,consumer指的是使用這些時鐘的模塊。

1. Linux時鐘框架基礎

相關文檔對時鐘框架做了詳細的介紹:《Linux common clock framework(1)_概述》、《Linux common clock framework(2)_clock provider》、《Linux common clock framework(3)_實現邏輯分析》以及《Common Clock Framework系統結構》。

這裏簡單羅列一下相關知識。

1.1 編寫時鐘provider驅動

provider包含基本硬件元素:Oscillator/Crystal-提供時鐘晶振、PLL-倍頻、Mux-多路選擇、Divider-分頻器、Gate-控制開關,還有Fixed-Divider-固定分頻器。

這些硬件都可以抽象成一種類型的時鐘,所有類型的時鐘都可以通過struct clk_hw描述。

struct clk_hw {
    struct clk_core *core;
    struct clk *clk;
    const struct clk_init_data *init;
};

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; unsigned
long rate; unsigned long req_rate; unsigned long new_rate; struct clk_core *new_parent; struct clk_core *new_child; unsigned long flags; bool orphan; unsigned int enable_count; unsigned int prepare_count; unsigned long min_rate; unsigned long max_rate; unsigned long accuracy; int phase; struct hlist_head children; struct hlist_node child_node; struct hlist_head clks; unsigned int notifier_count; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct hlist_node debug_node; #endif struct kref ref; }; struct clk_init_data { const char *name; const struct clk_ops *ops; const char * const *parent_names; u8 num_parents; unsigned long flags; };
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); int (*debug_init)(struct clk_hw *hw, struct dentry *dentry); };

clk_register()將描述時鐘的struct clk_hw註冊,轉化成strcut clk變量。

但在實際使用中,對不同類型的時鐘往往調用其對應的封裝函數。

對於上面提到的硬件在下面都能找到對應的註冊函數,其中包括一個composite設備作為一個組合註冊。

struct clk *clk_register(struct device *dev, struct clk_hw *hw)

int clk_hw_register(struct device *dev, struct clk_hw *hw)
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_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); 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_mux(struct device *dev, const char *name, const char * const *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_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div); 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); struct clk *clk_register_composite(struct device *dev, const char *name, const char * const *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);

最後調用of_clk_add_provider()將註冊的時鐘加入到OF框架中。

int of_clk_add_provider(struct device_node *np,
            struct clk *(*clk_src_get)(struct of_phandle_args *args,
                           void *data),
            void *data);

1.2 consumer使用時鐘

其他設備需要使用時鐘,可以再驅動中後去時鐘也可以在設備DTS中引用時鐘。

struct clk *clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get(struct device *dev, const char *id);
int clk_enable(struct clk *clk);
void clk_disable(struct clk *clk);
unsigned long clk_get_rate(struct clk *clk);
void clk_put(struct clk *clk);
void devm_clk_put(struct device *dev, struct clk *clk);
long clk_round_rate(struct clk *clk, unsigned long rate);
int clk_set_rate(struct clk *clk, unsigned long rate);
bool clk_has_parent(struct clk *clk, struct clk *parent);
int clk_set_rate_range(struct clk *clk, unsigned long min, unsigned long max);
int clk_set_min_rate(struct clk *clk, unsigned long rate);
int clk_set_max_rate(struct clk *clk, unsigned long rate);
int clk_set_parent(struct clk *clk, struct clk *parent);
struct clk *clk_get_parent(struct clk *clk);
struct clk *clk_get_sys(const char *dev_id, const char *con_id);

int clk_prepare(struct clk *clk);
void clk_unprepare(struct clk *clk);
static inline int clk_prepare_enable(struct clk *clk)
static inline void clk_disable_unprepare(struct clk *clk)
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);

2. 如何實現一款芯片的時鐘框架

對一款芯片配置時鐘框架,首先拿到時鐘框架圖,上面會有詳細的Mux關系、是否有Divider、是否是Fixed Divider、是否有gate等等。

將這些器件找到對應的Linux時鐘框架抽象,將整張時鐘框架圖抽象成Linux時鐘框架識別的屬性結構。

然後還需要每一個器件的寄存器解釋。

在有了這些準備工作之後,工作氛圍兩部分:編寫器件抽象驅動,比如Fixed clock、Gate、Divider等;按照時鐘框架圖編寫DTS文件,寄存器參照規格書,compatible和驅動對應。

2.1 編寫類型時鐘驅動

首先通過CLK_OF_DECLARE()將字符串和xx2000_divider_setup()進行關聯,然後在xx2000_divider_setup進行時鐘的註冊。

static void xx2000_divider_setup(struct device_node *node)
{
    void __iomem *reg;
    struct resource res;
    struct clk *clk;
    unsigned int bit_shift = 0, bit_width = 0;
    const char *clk_name = NULL;
    const char *parent_name;
    int ret = 0;

    if(!node)
        return;

    reg = of_io_request_and_map(node, 0, of_node_full_name(node));-----------------------------------將寄存器映射,後續對divider的設置以及讀取都需要此寄存器。
    if(IS_ERR(reg)) {
        pr_err("%s <%s> must have a reg property.\n", __func__, node->name);
        return;
    }

    if(of_property_read_u32(node, "bit-shift", &bit_shift)) {----------------------------------------操作divider需要知道配置divider的位偏移及位寬。然後根據頻率選擇divider的值,設置到寄存器中。獲取時鐘頻率也通過讀取寄存器值進行計算。
        pr_err("%s <%s> must have a bit-shift property.\n", __func__, node->name);
        goto err_unmap;
    }
    if(of_property_read_u32(node, "bit-width", &bit_width)) {
        pr_err("%s <%s> must have a bit-width property.\n", __func__, node->name);
        goto err_unmap;
    }

    parent_name = of_clk_get_parent_name(node, 0);----------------------------------------------------獲取父時鐘名稱。
    if(!parent_name)
    {
        pr_err("%s <%s> must have a parent.\n", __func__, node->name);
        goto err_unmap;
    }

    of_property_read_string(node, "clock-output-names", &clk_name);

    clk = clk_register_divider(NULL, clk_name, parent_name, 0, reg, bit_shift, bit_width, 0, NULL);---註冊divider時鐘,必須要有的參數有reg、bit_shift、bit_width,以及本身的名稱。
    if(IS_ERR(clk))
    {
        pr_err("%s Failed to register <%s>.\n", __func__, node->name);
        goto err_unmap;
    }

    ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);--------------------------------------將註冊的時鐘加入到OF框架。
    if(ret)
    {
        pr_err("%s Failed to add <%s>.\n", __func__, node->name);
        goto err_unregister;
    }

    return;
    
    err_unregister:
        clk_unregister_divider(clk);

    err_unmap:
        iounmap(reg);
        of_address_to_resource(node, 0, &res);
        release_mem_region(res.start, resource_size(&res));
    return;
}

CLK_OF_DECLARE(xx2000_clk_divider, "xx2000,clk-divider", xx2000_divider_setup);

2.2 編寫DTS文件

有了上面的時鐘框架圖、時鐘寄存器規格書和驅動,就可以按部就班的按照時鐘框架圖一步一步編寫DTS。

  • 編寫fixed clock的晶振、PLL等;
  • 編寫多路復用Mux和分頻器Divider,需要配置寄存器以及寄存器的bit-shift和bit-width。

具體的DTS配置,參考如下:

            cpu_core_clk: cpu-core-clk {---------------------------------cpu_core_clk是在其他設備中clocks指向的名稱。
                #clock-cells = <0>;--------------------------------------0表示只有一個輸出,1表示多余一個輸出。
                compatible = "xx2000,clk-divider";-----------------------如果有特殊需求,還需要編寫自己的驅動。這裏通過此字符串進行匹配。
                reg = <CPU_CLK_DIV 0x4>;---------------------------------配置此事中的寄存器地址以及大小。
                bit-shift = <0>;-----------------------------------------對於divider類型需要知道配置bit在寄存器中的偏移以及bit位寬。
                bit-width = <5>;
                clocks = <&cpu_mux 0>;-----------------------------------clocks指向父時鐘。
                clock-output-names = "cpu_core_clk";---------------------本時鐘輸出名稱,在consumer時鐘中可以使用此名稱來獲得該時鐘的struct clk結構體。
            };

3. 對時鐘框架進行驗證

3.1 clk_summary驗證時鐘樹

通過讀取/sys/kernel/debug/clk/clk_summary信息,和時鐘框圖對照,可以驗證DTS配置正確與否。

   clock                             enable  prepare_cnt        rate   accuracy   phase
---------------------------------------------------------------------------------------- ddr_pll                                  0            0  1200000000          0 0  
 nn_pll                                   0            0   750000000          0 0  
 video_pll                                0            0  1100000000          0 0  
    sdio0_mux                             0            0  1100000000          0 0  
       sdio0_cclk_divider                 0            0    39285715          0 0  
          sdio0_cclk                      0            0    39285715          0 0...
 cpu_pll                                  0            0  1000000000          0 0  
    cpu_mux                               0            0  1000000000          0 0  
       cpu_core_clk                       0            0  1000000000          0 0  
          cpu_bus_clk                     0            0   500000000          0 0  
             cpu_apb_clk                  0            0   250000000          0 0  
             ddr_cpu_port_clk             0            0   500000000          0 0  
 rtc_clk                                  0            0       32768          0 0  
    tsen_mux                              0            0       32768          0 0  
       tsen_clk                           0            0       32768          0 0  
 ref_clk                                  0            0    24000000          0 0  
    wdt_clk                               0            0    24000000          0 0  
    timer3_clk                            0            0    24000000          0 0  
    timer2_clk                            0            0    24000000          0 0  
    timer1_clk                            0            0    24000000          0 0  
    timer0_clk                            0            0    24000000          0 0  
    ref_clk_750_fixed_factor              0            0       32000          0 0  
       usb_suspend_clk                    0            0       32000          0 0  

3.2 驗證時鐘實際輸出

在/sys/kernel/debug/clk目錄下,每個時鐘都有自己的目錄。

在clk_debug_create_one()函數中,對divider和gate類型時鐘創建相應的節點用於控制硬件。

 static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
 {
        struct dentry *d;
@@ -2182,6 +2290,7 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
                if (ret)
                        goto err_out;
        }
+    xx2000_clk_create(core);
 
        ret = 0;
        goto out;

下面根據struct clk_core所對應的struct clk_ops來判斷時鐘的類型,gate創建xx2000_gate,divider創建xx2000_rate節點。

static ssize_t xx2000_gate_read(struct file *filp, char __user *buffer,
                    size_t count, loff_t *ppos)
{
    struct clk_core *pdata = filp->private_data;
    unsigned int value;
    char tmp[32];
    size_t size;

    value = __clk_is_enabled(pdata->hw->clk);
    size = sprintf(tmp, "%u\n", value);
    printk("%s value=%u\n", __func__, value);

    return simple_read_from_buffer(buffer, count, ppos, tmp, size);
}

static ssize_t xx2000_gate_write(struct file *filp,
                     const char __user *buffer,
                     size_t count, loff_t *ppos)
{
    struct clk_core *pdata = filp->private_data;
    unsigned int value;
    int ret = 0;

    ret = kstrtouint_from_user(buffer, count, 0, &value);
    if (ret)
        return -EFAULT;

    printk("%s name=%s value=%u\n", __func__, pdata->name, value);

    if(value)
        clk_prepare_enable(pdata->hw->clk);
    else
        clk_disable_unprepare(pdata->hw->clk);
    return count;
}

static const struct file_operations xx2000_gate_ops = {
    .owner = THIS_MODULE,
    .open = simple_open,
    .read = xx2000_gate_read,
    .write = xx2000_gate_write,
    .release = single_release,
};

static ssize_t xx2000_rate_read(struct file *filp, char __user *buffer,
                    size_t count, loff_t *ppos)
{
    struct clk_core *pdata = filp->private_data;
    unsigned long rate;
    char tmp[32];
    size_t size;

    rate = clk_get_rate(pdata->hw->clk);
    size = sprintf(tmp, "%lu\n", rate);
    printk("%s value=%lu\n", __func__, rate);

    return simple_read_from_buffer(buffer, count, ppos, tmp, size);
}

static ssize_t xx2000_rate_write(struct file *filp,
                     const char __user *buffer,
                     size_t count, loff_t *ppos)
{
    struct clk_core *pdata = filp->private_data;
    unsigned int rate;
    int ret = 0;

    ret = kstrtouint_from_user(buffer, count, 0, &rate);
    if (ret)
        return -EFAULT;

    printk("%s value=%u\n", __func__, rate);

    if(rate)
        clk_set_rate(pdata->hw->clk, rate);
    return count;
}

static const struct file_operations xx2000_rate_ops = {
    .owner = THIS_MODULE,
    .open = simple_open,
    .read = xx2000_rate_read,
    .write = xx2000_rate_write,
    .release = single_release,
};

void xx2000_clk_create(struct clk_core *core)
{
    const struct clk_ops *clk_ops = core->ops;

    //printk("%s %s %p %p %p %p\n", __func__, core->name ,clk_ops, &clk_gate_ops, &clk_mux_ops, &clk_divider_ops);
    if(clk_ops == &clk_gate_ops)
    {
        debugfs_create_file("xx2000_gate", S_IRUSR | S_IWUSR, core->dentry, core, &xx2000_gate_ops);
    }
    else if(clk_ops == &clk_mux_ops)
    {
//        debugfs_create_file("xx2000_mux", S_IRUSR | S_IWUSR, core->dentry, core, &xx2000_mux_ops);
    }
    else if(clk_ops == &clk_divider_ops)
    {
        debugfs_create_file("xx2000_rate", S_IRUSR | S_IWUSR, core->dentry, core, &xx2000_rate_ops);
    }
}

選擇合適的clk輸出pin,對上面的不同時鐘進行開關、頻率選擇。

可以通過clk_summary查看結果;還可以通過測量pin輸出波形驗證結果是否正確。

4. 小結

Linux提供了良好的時鐘框架,wowotec.net對其進行了良好的總結。

在實際應用中,通過時鐘框架圖對時鐘樹進行抽象,結合時鐘規格書配置時鐘樹;編寫時鐘驅動。

然後查看clk_summary,並進行驗證;最後在相應的設備驅動中使用時鐘。

Linux下時鐘框架實踐---一款芯片的時鐘樹配置