一文搞懂 | Linux 时钟子系统
Clock 时钟就是 SoC 中的脉搏,由它来控制各个部件按各自的节奏跳动。比如,CPU主频设置,串口的波特率设置,I2S的采样率设置,I2C的速率设置等等。这些不同的clock设置,都需要从某个或某几个时钟源头而来,最终开枝散叶,形成一颗时钟树。可通过 cat /sys/kernel/debug/clk/clk_summary 查看这棵时钟树。
内核中用 CCF 框架来管理 clock,如下所示,右边是 clock 提供者,即 Clock Provider;中间是 CCF;左边是设备驱动的 clock 使用者,即 Clock Consumer。
Clock Provider
根节点一般是 Oscillator(有源振荡器)或者 Crystal(无源振荡器)。
中间节点有很多种,包括 PLL(锁相环,用于提升频率的),Divider(分频器,用于降频的),Mux(从多个clock path中选择一个),Gate(用来控制ON/OFF的)。
叶节点是使用 clock 做为输入的、有具体功能的 HW block。
根据 clock 的特点,clock framework 将 clock 分为 fixed rate、gate、devider、mux、fixed factor、composite 六类。
数据结构
上面六类本质上都属于clock device,内核把这些 clock HW block 的特性抽取出来,用 struct clk_hw 来表示,具体如下:
struct clk_hw {//指向CCF模块中对应 clock device 实例struct clk_core *core;//clk是访问clk_core的实例。每当consumer通过clk_get对CCF中的clock device(也就是clk_core)发起访问的时候都需要获取一个句柄,也就是clkstruct clk *clk;//clock provider driver初始化时的数据,数据被用来初始化clk_hw对应的clk_core数据结构。const struct clk_init_data *init;
};struct clk_init_data {//该clock设备的名字const char *name;//clock provider driver进行具体的 HW 操作const struct clk_ops *ops;//描述该clk_hw的拓扑结构const char * const *parent_names;const struct clk_parent_data *parent_data;const struct clk_hw **parent_hws;u8 num_parents;unsigned long flags;
};
以固定频率的振动器 fixed rate 为例,它的数据结构是:
struct clk_fixed_rate {//下面是fixed rate这种clock device特有的成员struct clk_hw hw;//基类unsigned long fixed_rate;unsigned long fixed_accuracy;u8 flags;
};
其他的特定的clock device大概都是如此,这里就不赘述了。
这里用一张图描述这些数据结构之间的关系:
注册方式
理解了数据结构,我们再看下每类 clock device 的注册方式。
1. fixed rate clock
这一类clock具有固定的频率,不能开关、不能调整频率、不能选择parent,是最简单的一类clock。可以直接通过 DTS 配置的方式支持。也可以通过接口,可以直接注册 fixed rate clock,如下:
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);struct clk *clk_register_fixed_rate(struct device *dev, const char *name,const char *parent_name, unsigned long flags,unsigned long fixed_rate);
2. gate clock
这一类clock只可开关(会提供.enable/.disable回调),可使用下面接口注册:
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);
3. divider clock
这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调),可通过下面两个接口注册:
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_divider_table(struct device *dev, const char *name,const char *parent_name, unsigned long flags,void __iomem *reg, u8 shift, u8 width,u8 clk_divider_flags, const struct clk_div_table *table,spinlock_t *lock);
4. mux clock
这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调,可通过下面两个接口注册:
struct clk *clk_register_mux(struct device *dev, const char *name,const char **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_mux_table(struct device *dev, const char *name,const char **parent_names, u8 num_parents, unsigned long flags,void __iomem *reg, u8 shift, u32 mask,u8 clk_mux_flags, u32 *table, spinlock_t *lock);
5. fixed factor clock
这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调。可通过下面接口注册:
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);
6. composite clock
顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:
struct clk *clk_register_composite(struct device *dev, const char *name,const char **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);
这些注册函数最终都会通过函数 clk_register 注册到 Common Clock Framework 中,返回为 struct clk 指针。如下所示:
然后将返回的 struct clk 指针,保存在一个数组中,并调用 of_clk_add_provider 接口,告知 Common Clock Framework。
Clock Consumer
获取 clock
即通过 clock 名称获取 struct clk 指针的过程,由 clk_get、devm_clk_get、clk_get_sys、of_clk_get、of_clk_get_by_name、of_clk_get_from_provider 等接口负责实现,这里以 clk_get 为例,分析其实现过程:
struct clk *clk_get(struct device *dev, const char *con_id)
{const char *dev_id = dev ? dev_name(dev) : NULL;struct clk *clk;if (dev) {//通过扫描所有“clock-names”中的值,和传入的name比较,如果相同,获得它的index(即“clock-names”中的第几个),调用of_clk_get,取得clock指针。clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)return clk;}return clk_get_sys(dev_id, con_id);
}
struct clk *of_clk_get(struct device_node *np, int index)
{struct of_phandle_args clkspec;struct clk *clk;int rc;if (index < 0)return ERR_PTR(-EINVAL);rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,&clkspec);if (rc)return ERR_PTR(rc);//获取clock指针clk = of_clk_get_from_provider(&clkspec);of_node_put(clkspec.np);return clk;
}
of_clk_get_from_provider 通过便利 of_clk_providers 链表,并调用每一个 provider 的 get 回调函数,获取 clock 指针。如下:
struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
{struct of_clk_provider *provider;struct clk *clk = ERR_PTR(-ENOENT);/* Check if we have such a provider in our array */mutex_lock(&of_clk_lock);list_for_each_entry(provider, &of_clk_providers, link) {if (provider->node == clkspec->np)clk = provider->get(clkspec, provider->data);if (!IS_ERR(clk))break;}mutex_unlock(&of_clk_lock);return clk;
}
至此,Consumer 与 Provider 里讲的 of_clk_add_provider 对应起来了。
操作 clock
//启动clock前的准备工作/停止clock后的善后工作。可能会睡眠。
int clk_prepare(struct clk *clk)
void clk_unprepare(struct clk *clk)//启动/停止clock。不会睡眠。
static inline int clk_enable(struct clk *clk)
static inline void clk_disable(struct clk *clk)//clock频率的获取和设置
static inline unsigned long clk_get_rate(struct clk *clk)
static inline int clk_set_rate(struct clk *clk, unsigned long rate)
static inline long clk_round_rate(struct clk *clk, unsigned long rate)//获取/选择clock的parent clock
static inline int clk_set_parent(struct clk *clk, struct clk *parent)
static inline struct clk *clk_get_parent(struct clk *clk)//将clk_prepare和clk_enable组合起来,一起调用。将clk_disable和clk_unprepare组合起来,一起调用
static inline int clk_prepare_enable(struct clk *clk)
static inline void clk_disable_unprepare(struct clk *clk)
总结
5T技术资源大放送!包括但不限于:C/C++,Arm, Linux,Android,人工智能,单片机,树莓派,等等。在上面的【人人都是极客】公众号内回复「peter」,即可免费获取!!
记得点击分享、赞和在看,给我充点儿电吧
一文搞懂 | Linux 时钟子系统相关推荐
- 一文搞定 | Linux 网络子系统
今天分享一篇经典Linux协议栈文章,主要讲解Linux网络子系统,看完相信大家对协议栈又会加深不少,不光可以了解协议栈处理流程,方便定位问题,还可以学习一下怎么去设计一个可扩展的子系统,屏蔽不同层次 ...
- 一文搞懂linux时间片,硬件时钟,软件时钟,实时时钟,时间中断,墙上时间
时间片: 时间片是一个数值,它表明程序在被抢占前所持续运行的时间. 相对时间绝对时间区别: 如果某个时间在5s后呗调度执行,那么系统所需要的不是绝对时间,而是相对时间(比如,相对现在5s后): ...
- 一文搞懂 | Linux 驱动的来龙去脉
驱动相关的学习资料网上很多,但大部分都是碎片化的记录,很少有系统化的总结整理.本文旨在系统化的讲清楚 Linux 驱动的来龙去脉.先从总线,设备,驱动介绍内核对于驱动的模型设计:然后引入设备树的概念, ...
- 一文搞懂Linux内核怎么提升UDP收包的效率
现在很多人都在诟病Linux内核协议栈收包效率低,不管他们是真的懂还是一点都不懂只是听别人说的,反正就是在一味地怼Linux内核协议栈,他们的武器貌似只有DPDK. 但是,即便Linux内核协议栈收包 ...
- 一文搞懂linux的proc文件
目录 proc文件夹是干嘛用? proc下都有什么系统信息? /proc/bus /proc/buddyinfo /proc/cgroups /proc/cmdline /proc/consoles ...
- fseek linux 大文件_一文搞懂Linux系统开发
文章目录 Linux系统开发会用到什么? C语言基础 shell脚本 慢慢学会使用Makefile 常规Linux系统编程知识都有什么?哪些常用?哪些不常用? 常规Linux编程知识 文件IO 文件与 ...
- linux程序打桩,一文搞懂linux的库打桩
Linux下的链接器支持一个强大的库打桩(library interpositioning),允许你阻拦对系统标准库中某个目标函数的调用,取而代之执行自己的包装函数.它可以给我们带来两个好处,一是通过 ...
- 一文搞懂Linux系统内核升级及下载当前内核源代码
1. 下载当前内核源代码 为什么下载内核源代? 一是便于查看或学习linux内核代码的底层实现方法: 二是便于编写或调试Linux设备驱动程序,因为驱动程序的编译需要内核源代码. 怎么下载? 如果你是 ...
- 一文搞懂Linux 内存管理原理
导语 linux 内存是后台开发人员,需要深入了解的计算机资源.合理的使用内存,有助于提升机器的性能和稳定性.本文主要介绍 linux 内存组织结构和页面布局,内存碎片产生原因和优化算法,linux ...
- 一文搞懂Linux rm命令 删除文件/文件夹
文章目录 一 rm命令简介 二 rm命令通用格式 三 使用示例 删除文件/文件夹 3.1 rm删除文件 3.2 rm删除文件夹 四 总结 一 rm命令简介 rm命令是Linux系统的一个命令.rm命令 ...
最新文章
- 意念实时转语音!Facebook的非植入式脑机接口,解码准确率达到76%
- aspmysql发布_ASP.NET Entity Framework with MySql服务器发布环境配置
- 使用Hexo搭建博客,备份至GitHub过程(基于网上资料的实践操作)
- 业务逻辑数据层SqlDataSourcesql的输入参数控件参数System.Web.UI.WebControls.GridView.SelectedValue...
- c语言中ai是什么,科普 | 什么是 cDai?
- 【分享】心理测试---家庭画像
- python 小程序搜索排名-用python2.7.9 写个小程序搜索某个目录下行有某关键字
- 【408考研计划】计算机组成原理
- 怎么恢复优盘里隐藏的数据 u盘隐藏数据恢复教程
- RSA/数字证书/签名原理详解
- SAP 移动类型详解 大全说明
- 爱分享 IE地址栏显示空白?360电脑救援巧修复
- 模拟城市我是市长服务器维护多久,《模拟城市:我是市长》教你一招渡过新手困难期...
- 【python】查找array中非零元素的横纵坐标
- Spring Security技术栈学习笔记(十三)Spring Social集成第三方登录验证开发流程介绍
- 【Spark NLP】第 7 章:分类和回归
- (解决)org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)问题的办法
- java实现打字母小游戏
- 事务压缩 对表的影响 compress for oltp
- 字符串右移n位,例如 “hello world“ 右移两位 后ldhello wor 要求写一个方法实现此功能,方法的格式是 String moveToRight(String str,int po