Linux下时钟框架实践---一款芯片的时钟树配置
关键词:时钟、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 = , bit_width = ;
const char *clk_name = NULL;
const char *parent_name;
int ret = ; if(!node)
return; reg = of_io_request_and_map(node, , 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, );----------------------------------------------------获取父时钟名称。
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, , reg, bit_shift, bit_width, , 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, , &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
nn_pll
video_pll
sdio0_mux
sdio0_cclk_divider
sdio0_cclk ...
cpu_pll
cpu_mux
cpu_core_clk
cpu_bus_clk
cpu_apb_clk
ddr_cpu_port_clk
rtc_clk
tsen_mux
tsen_clk
ref_clk
wdt_clk
timer3_clk
timer2_clk
timer1_clk
timer0_clk
ref_clk_750_fixed_factor
usb_suspend_clk
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;
@@ -, +, @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
if (ret)
goto err_out;
}
+xx2000_clk_create(core); ret = ;
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[];
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 = ; ret = kstrtouint_from_user(buffer, count, , &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[];
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 = ; ret = kstrtouint_from_user(buffer, count, , &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提供了良好的时钟框架,wowotech.net对其进行了详细的总结。
在实际应用中,通过时钟框架图对时钟树进行抽象,结合时钟规格书配置时钟树;编写时钟驱动。
然后查看clk_summary,并进行验证;最后在相应的设备驱动中使用时钟。
Linux下时钟框架实践---一款芯片的时钟树配置的更多相关文章
- linux 通用时钟框架CCF
linux CCF 时钟框架 简单介绍 这里讲的时钟是给soc各组件提供时钟的树状框架,并非内核使用的时间,和其它模块一样,clk也有框架,用以适配不同的平台.适配层之上是客户代码和接口,也就是各模块 ...
- linux 时钟源初步分析linux kernel 时钟框架详细介绍
初步概念: 看datasheet的关于时钟与定时器的部分, FCLK供给cpu, HCLK供给AHB总线设备(存储器控制器,中断控制器.LCD控制器.DMA.USB主机控制器等), PCLK供给APB ...
- linux下ejabberd框架搭建
ejabberd为erlang的IM的开源框架,一直想找个时间研究研究: 1.下载Ejabberd安装包 wget http://www.process-one.net/downloads/ejabb ...
- Windows和Linux下scrapy框架的安装
windows下安装: 1.安装Anaconda环境管理工具 也可以使用pip安装,值得注意的是如果你使用的是pip安装,你需要解决相应的包依赖(解决依赖一般会让你怀疑人生.怀疑scrapy,建议还是 ...
- linux下, 再次遇到使用thinkphp的模板标签时,报错used undefined function \Think\Template\simplexml_load_string() 是因为没有安装 php-xml包
linux下, 使用thinkphp的模板标签,如 eq, gt, volist defined, present , empty等 标签时, 报错: used undefined function ...
- Linux下(centos6.8)JDK1.8的安装与配置
今天说下在Linux(centos6.8)系统下的JDK安装与配置. 据我所知的jdk安装方式有三种(rpm.yum方式没用过,暂且不提)今天只说解压安装方式: 一.解压jdk安装包: 附上jdk1. ...
- linux下在用python向文件写入数据时'\n'不起作用
网上翻看一圈,大家都说利用write写数据换行,在linux下用'\n',windows下利用'\r\n',可是尝试了一下,'\n'在windows底下可换行,在linux底下居然不起作用,最后利用' ...
- Android/Linux下CGroup框架分析及其使用
1 cgroup介绍 CGroup是control group的简称,它为Linux kernel提供一种任务聚集和划分的机制,可以限制.记录.隔离进程组(process groups)所使用的资源( ...
- Linux下Spark框架配置(Python)
简述 Spark是UC Berkeley AMP lab所开源的类Hadoop MapReduce的通用并行框架,Spark,拥有Hadoop MapReduce所具有的优点:但不同于MapRedu ...
随机推荐
- LeetCode专题-Python实现之第28题: Implement strStr()
导航页-LeetCode专题-Python实现 相关代码已经上传到github:https://github.com/exploitht/leetcode-python 文中代码为了不动官网提供的初始 ...
- Java开发知识之Java数组
Java开发知识之Java数组 一丶数组简介 首先,不管是Java 还是 C++ 还是其它语言.都有数组. (有可能叫法不同) 数组简而言之就是存储一段连续相同数据类型的数据结构 在Java中数组可以 ...
- PE知识复习之PE的导出表
PE知识复习之PE的导出表 一丶简介 在说明PE导出表之前.我们要理解.一个PE可执行程序.是由一个文件组成的吗. 答案: 不是.是由很多PE文件组成.DLL也是PE文件.如果我们PE文件运行.那么就 ...
- 如何将视频导入到ipad中并播放
首先在电脑上下载并安装itunes,然后用apple账号登入, 在ipad上从apple store中下载一个播放器如KMPlayer 点击itunes上小手机的图标,找到文件共享,选中应用KMPla ...
- 装饰器模式 Decorator 结构型 设计模式 (十)
引子 现实世界的装饰器模式 大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介 "老板,来一个手抓饼, 加个培根, 加个鸡蛋,多少钱?" 这句话会不 ...
- 痞子衡嵌入式:串口调试工具Jays-PyCOM诞生记(6)- 打包发布(PyInstaller3.3.1)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是串口调试工具Jays-PyCOM诞生之打包发布. 经过上一篇软件优化之后,Jays-PyCOM已经初长成,该到了出去历练的时候了,只有经 ...
- BootStrap之 提示工具(Tooltip)插件
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- Java 学习笔记 二维数组和对象数组
定义二维数组 int[][] a = new int[4][5]; 可以不指定列数 int[][] a = new int[4][]; 获取行 int i = a.length(); 如果使用第一个例 ...
- html的标签分类————可以上传的数据篇
html的标签可以分为: 块级标签:div(白板),H系列(加大加粗,H1—H7,字体一般逐渐变小,一般用作标题),p标签(段落之间有间距) 行内标签:span(白板) 此外,标签之间是可以嵌套的.为 ...
- ArcGIS for JavaScript学习(二)Server发布服务
一 ArcGIS for Server 安装.配置 (1)双击setup (2)点击下一步完成安装 (3)配置 a 登录Manager 开始—>程序—>ArcGIS—>Manager ...