全部博文(396)
分类: 嵌入式
2016-09-22 21:57:31
linux clk时钟源管理驱动代码
1. 硬件资源越来越庞大和复杂,内核的另一个挑战就是要便捷的管理这些资源。同时,面对如此之多的平台不同的CPU,管理机制需要统一适用,这就需要对资源的管理抽象到更加通用的层次。CPU中各个模块都需要时钟clock,内核需要一种机制能通用于所有的平台,方便的管理CPU上所有的clock资源。这里分析Linux对clock的管理。即Linux最新版本添加的COMMON_CLOCK通用时钟架构。
Linux version: 3.14.1,涉及的源文件有:
include/linux/clk.h 这个是驱动中需要包含的头文件,包含了所有驱动可以使用的clock接口API,驱动开发中需要引入这个头文件。
drivers/clk/clk.h 几乎没用,就两行代码。
drivers/clk/clk.c 这个是clock接口的实现文件,include/linux/clk.h中声明的所有接口函数都在这个文件中实现。包括clock的注册、clock频率设置、clock父子关系设置、clock复用设置等。
drivers/clk/clkdev.c这个文件实现了clock的查找表项的建立和查找。实现了典型的clkdev_add 和clk_get。
2. 时钟通用架构的几个重要数据结构
struct clk {
const char *name;
const struct clk_ops *ops;
struct clk_hw *hw;
struct module *owner;
struct clk *parent;
const char **parent_names;
struct clk **parents;
u8 num_parents;
u8 new_parent_index;
unsigned long rate;
unsigned long new_rate;
struct clk *new_parent;
struct clk *new_child;
unsigned long flags;
unsigned int enable_count;
unsigned int prepare_count;
unsigned long accuracy;
struct hlist_head children;
struct hlist_node child_node;
unsigned int notifier_count;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
#endif
struct kref ref;
};
这个结构定义在clk-private.h中,clk-private.h在任何情况下都不应该在自己的源文件中引用。几乎只有drivers/clk/clk.c会包含clk-private.h。这个结构不包含任何hardware-specific的成员,但是如何通向hardware层呢?就是通过ops和hw两个成员。ops指向了某个clock的hardware-specific的操作函数,hw则作为操作函数的参数用于获取clock的hardware-specific的数据,[hardware-specific的操作函数]+[hardware-specific的数据]=[对clock的寄存器的操作]。
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, unsigned long *);
long (*determine_rate)(struct clk_hw *hw, unsigned long rate, unsigned long *best_parent_rate, struct clk **best_parent_clk);
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, unsigned long);
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);
void (*init)(struct clk_hw *hw);
};
这个结构显然用于指向clock的hardware-specific的操作函数,clock驱动中根据某个clock的硬件特性需要实现部分操作函数。例如,PLL锁相环的父时钟一般是高频振荡器,输出时钟频率一般来说也是固定的或者说是不需要动态修改的,那么就只要实现enable/disable/set_rate/recalc_rate差不多就可以了。
struct clk_init_data {
const char *name;
const struct clk_ops *ops;
const char **parent_names;
u8 num_parents;
unsigned long flags;
};
clock的初始化数据,在clock驱动中注册clock需为clock提供必要的信息。name初始化为时钟的名字,可以用于clk_get;ops指向clock驱动中实现的操作函数;如果某个clock有多个可选的父时钟,parent_names是这些父时钟的名字数组;num_parents父时钟名字数组中父时钟的个数;flags时钟的特性标记,可以参见clk-provider.h最开始部分的特性集合。
struct clk_hw {
struct clk *clk;
const struct clk_init_data *init;
};
这个clk_hw结构用于联系common部分和hardware-specific部分。任何ops结构中的操作函数的参数都带有clk_hw结构指针。
struct clk_xxx {
struct clk_hw hw;
void __iomem *reg;
u8 shift;
u8 width;
u8 flags;
spinlock_t *lock;
};
这个结构是一个自定义的时钟结构,clock驱动中需要自己定义满足平台需要的时钟结构,成员hw是必须的,其他成员都是根据时钟硬件的特性按需添加hardware-specific的数据。正是由于hw是自定义时钟结构必须的成员,那么总是可以用过定义#define to_clk_xxx(_hw) container_of(_hw, struct clk_xxx, hw)在ops的操作函数中通过to_clk_xxx(_hw)来获取自定义时钟结构,即获取clock的hardware-specific的数据,因为前面已经说过ops的函数中都有一个hw作为参数。
3. clocks链表以及查找
每个clk结构还要对应一个struct clk_lookup结构。在初始化时,会将所有的clk_loopup结构添加进入clocks链表中,定义在drivers/clk/clkdev.c。
struct clk_lookup {
struct list_head node;
const char *dev_id;
const char *con_id;
struct clk *clk;
};
clk_lookup,顾名思义就知道它是用来查找struct clk结构的。有了它,就可以通过设备名或时钟源的名字来找到相应的struct clk结构。链表操作位于drivers/clk/clkdev.c
4. clock与pm
为了省电,当不需要clk时将其关闭,上面的clk_enable/clk_disable实现了此功能。除了关闭clk省电,还可以降低clk频率以达到省电的目的。当系统当前负载较轻,不需要clk跑在那么高的频率时,就可以对该clk降频了。从这些关系可以看到,clk与电源管理,cpufreq等都可能有关联。
5. clock驱动开发中只需要引入clk-provider.h文件就OK。