Linux下时钟框架实践---一款芯片的时钟树配置
關(guān)鍵詞:時鐘、PLL、Mux、Divider、Gate、clk_summary等。
時鐘和電源是各種設(shè)備的基礎(chǔ)設(shè)施,整個時鐘框架可以抽象為幾種基本的元器件:負(fù)責(zé)提供晶振
Linux內(nèi)核提供了良好的CCF(Common Clock Framework),框架的兩端一個是provider,一個是consumer。
provider指的是提供時鐘模塊,包括晶振、PLL、Mux、Divider、Gate等,consumer指的是使用這些時鐘的模塊。
1. Linux時鐘框架基礎(chǔ)
相關(guān)文檔對時鐘框架做了詳細(xì)的介紹:《Linux common clock framework(1)_概述》、《Linux common clock framework(2)_clock provider》、《Linux common clock framework(3)_實(shí)現(xiàn)邏輯分析》以及《Common Clock Framework系統(tǒng)結(jié)構(gòu)》。
這里簡單羅列一下相關(guān)知識。
1.1 編寫時鐘provider驅(qū)動
provider包含基本硬件元素:Oscillator/Crystal-提供時鐘晶振、PLL-倍頻、Mux-多路選擇、Divider-分頻器、Gate-控制開關(guān),還有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注冊,轉(zhuǎn)化成strcut clk變量。
但在實(shí)際使用中,對不同類型的時鐘往往調(diào)用其對應(yīng)的封裝函數(shù)。
對于上面提到的硬件在下面都能找到對應(yīng)的注冊函數(shù),其中包括一個composite設(shè)備作為一個組合注冊。
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);
最后調(diào)用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使用時鐘
其他設(shè)備需要使用時鐘,可以再驅(qū)動中后去時鐘也可以在設(shè)備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. 如何實(shí)現(xiàn)一款芯片的時鐘框架
對一款芯片配置時鐘框架,首先拿到時鐘框架圖,上面會有詳細(xì)的Mux關(guān)系、是否有Divider、是否是Fixed Divider、是否有g(shù)ate等等。
將這些器件找到對應(yīng)的Linux時鐘框架抽象,將整張時鐘框架圖抽象成Linux時鐘框架識別的屬性結(jié)構(gòu)。
然后還需要每一個器件的寄存器解釋。
在有了這些準(zhǔn)備工作之后,工作氛圍兩部分:編寫器件抽象驅(qū)動,比如Fixed clock、Gate、Divider等;按照時鐘框架圖編寫DTS文件,寄存器參照規(guī)格書,compatible和驅(qū)動對應(yīng)。
2.1 編寫類型時鐘驅(qū)動
首先通過CLK_OF_DECLARE()將字符串和xx2000_divider_setup()進(jìn)行關(guān)聯(lián),然后在xx2000_divider_setup進(jìn)行時鐘的注冊。
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));-----------------------------------將寄存器映射,后續(xù)對divider的設(shè)置以及讀取都需要此寄存器。
if(IS_ERR(reg)) {
pr_err("%s <%s> must have a reg property.
", __func__, node->name);
return;
}
if(of_property_read_u32(node, "bit-shift", &bit_shift)) {----------------------------------------操作divider需要知道配置divider的位偏移及位寬。然后根據(jù)頻率選擇divider的值,設(shè)置到寄存器中。獲取時鐘頻率也通過讀取寄存器值進(jìn)行計(jì)算。
pr_err("%s <%s> must have a bit-shift property.
", __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.
", __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.
", __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時鐘,必須要有的參數(shù)有reg、bit_shift、bit_width,以及本身的名稱。
if(IS_ERR(clk))
{
pr_err("%s Failed to register <%s>.
", __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>.
", __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文件
有了上面的時鐘框架圖、時鐘寄存器規(guī)格書和驅(qū)動,就可以按部就班的按照時鐘框架圖一步一步編寫DTS。
編寫fixed clock的晶振、PLL等;
編寫多路復(fù)用Mux和分頻器Divider,需要配置寄存器以及寄存器的bit-shift和bit-width。
具體的DTS配置,參考如下:
cpu_core_clk: cpu-core-clk {---------------------------------cpu_core_clk是在其他設(shè)備中clocks指向的名稱。
#clock-cells = <0>;--------------------------------------0表示只有一個輸出,1表示多余一個輸出。
compatible = "xx2000,clk-divider";-----------------------如果有特殊需求,還需要編寫自己的驅(qū)動。這里通過此字符串進(jìn)行匹配。
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結(jié)構(gòu)體。
};
3. 對時鐘框架進(jìn)行驗(yàn)證
3.1 clk_summary驗(yàn)證時鐘樹
通過讀取/sys/kernel/debug/clk/clk_summary信息,和時鐘框圖對照,可以驗(yàn)證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 驗(yàn)證時鐘實(shí)際輸出
在/sys/kernel/debug/clk目錄下,每個時鐘都有自己的目錄。
在clk_debug_create_one()函數(shù)中,對divider和gate類型時鐘創(chuàng)建相應(yīng)的節(jié)點(diǎn)用于控制硬件。
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;
下面根據(jù)struct clk_core所對應(yīng)的struct clk_ops來判斷時鐘的類型,gate創(chuàng)建xx2000_gate,divider創(chuàng)建xx2000_rate節(jié)點(diǎn)。
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
", value);
printk("%s value=%u
", __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
", __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
", rate);
printk("%s value=%lu
", __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
", __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
", __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,對上面的不同時鐘進(jìn)行開關(guān)、頻率選擇。
可以通過clk_summary查看結(jié)果;還可以通過測量pin輸出波形驗(yàn)證結(jié)果是否正確。
4. 小結(jié)
Linux提供了良好的時鐘框架,wowotech.net對其進(jìn)行了詳細(xì)的總結(jié)。
在實(shí)際應(yīng)用中,通過時鐘框架圖對時鐘樹進(jìn)行抽象,結(jié)合時鐘規(guī)格書配置時鐘樹;編寫時鐘驅(qū)動。
然后查看clk_summary,并進(jìn)行驗(yàn)證;最后在相應(yīng)的設(shè)備驅(qū)動中使用時鐘。
總結(jié)
以上是生活随笔為你收集整理的Linux下时钟框架实践---一款芯片的时钟树配置的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。