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;
}

至此,ConsumerProvider 里讲的 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 时钟子系统相关推荐

  1. 一文搞定 | Linux 网络子系统

    今天分享一篇经典Linux协议栈文章,主要讲解Linux网络子系统,看完相信大家对协议栈又会加深不少,不光可以了解协议栈处理流程,方便定位问题,还可以学习一下怎么去设计一个可扩展的子系统,屏蔽不同层次 ...

  2. 一文搞懂linux时间片,硬件时钟,软件时钟,实时时钟,时间中断,墙上时间

    时间片: ​ 时间片是一个数值,它表明程序在被抢占前所持续运行的时间. 相对时间绝对时间区别: ​ 如果某个时间在5s后呗调度执行,那么系统所需要的不是绝对时间,而是相对时间(比如,相对现在5s后): ...

  3. 一文搞懂 | Linux 驱动的来龙去脉

    驱动相关的学习资料网上很多,但大部分都是碎片化的记录,很少有系统化的总结整理.本文旨在系统化的讲清楚 Linux 驱动的来龙去脉.先从总线,设备,驱动介绍内核对于驱动的模型设计:然后引入设备树的概念, ...

  4. 一文搞懂Linux内核怎么提升UDP收包的效率

    现在很多人都在诟病Linux内核协议栈收包效率低,不管他们是真的懂还是一点都不懂只是听别人说的,反正就是在一味地怼Linux内核协议栈,他们的武器貌似只有DPDK. 但是,即便Linux内核协议栈收包 ...

  5. 一文搞懂linux的proc文件

    目录 proc文件夹是干嘛用? proc下都有什么系统信息? /proc/bus /proc/buddyinfo /proc/cgroups /proc/cmdline /proc/consoles ...

  6. fseek linux 大文件_一文搞懂Linux系统开发

    文章目录 Linux系统开发会用到什么? C语言基础 shell脚本 慢慢学会使用Makefile 常规Linux系统编程知识都有什么?哪些常用?哪些不常用? 常规Linux编程知识 文件IO 文件与 ...

  7. linux程序打桩,一文搞懂linux的库打桩

    Linux下的链接器支持一个强大的库打桩(library interpositioning),允许你阻拦对系统标准库中某个目标函数的调用,取而代之执行自己的包装函数.它可以给我们带来两个好处,一是通过 ...

  8. 一文搞懂Linux系统内核升级及下载当前内核源代码

    1. 下载当前内核源代码 为什么下载内核源代? 一是便于查看或学习linux内核代码的底层实现方法: 二是便于编写或调试Linux设备驱动程序,因为驱动程序的编译需要内核源代码. 怎么下载? 如果你是 ...

  9. 一文搞懂Linux 内存管理原理

    导语 linux 内存是后台开发人员,需要深入了解的计算机资源.合理的使用内存,有助于提升机器的性能和稳定性.本文主要介绍 linux 内存组织结构和页面布局,内存碎片产生原因和优化算法,linux ...

  10. 一文搞懂Linux rm命令 删除文件/文件夹

    文章目录 一 rm命令简介 二 rm命令通用格式 三 使用示例 删除文件/文件夹 3.1 rm删除文件 3.2 rm删除文件夹 四 总结 一 rm命令简介 rm命令是Linux系统的一个命令.rm命令 ...

最新文章

  1. 意念实时转语音!Facebook的非植入式脑机接口,解码准确率达到76%
  2. aspmysql发布_ASP.NET Entity Framework with MySql服务器发布环境配置
  3. 使用Hexo搭建博客,备份至GitHub过程(基于网上资料的实践操作)
  4. 业务逻辑数据层SqlDataSourcesql的输入参数控件参数System.Web.UI.WebControls.GridView.SelectedValue...
  5. c语言中ai是什么,科普 | 什么是 cDai?
  6. 【分享】心理测试---家庭画像
  7. python 小程序搜索排名-用python2.7.9 写个小程序搜索某个目录下行有某关键字
  8. 【408考研计划】计算机组成原理
  9. 怎么恢复优盘里隐藏的数据 u盘隐藏数据恢复教程
  10. RSA/数字证书/签名原理详解
  11. SAP 移动类型详解 大全说明
  12. 爱分享 IE地址栏显示空白?360电脑救援巧修复
  13. 模拟城市我是市长服务器维护多久,《模拟城市:我是市长》教你一招渡过新手困难期...
  14. 【python】查找array中非零元素的横纵坐标
  15. Spring Security技术栈学习笔记(十三)Spring Social集成第三方登录验证开发流程介绍
  16. 【Spark NLP】第 7 章:分类和回归
  17. (解决)org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)问题的办法
  18. java实现打字母小游戏
  19. 事务压缩 对表的影响 compress for oltp
  20. 字符串右移n位,例如 “hello world“ 右移两位 后ldhello wor 要求写一个方法实现此功能,方法的格式是 String moveToRight(String str,int po

热门文章

  1. Unity 模拟鼠标自动点击事件
  2. 使用 emoji表情 实现自己的 表情库
  3. 微信小程序影视评论交流平台系统毕业设计毕设(6)开题答辩PPT
  4. android 界面置顶,Android实现界面滚动时顶部部分内容置顶(附源码)
  5. 基于StockRanker的AI量化选股策略
  6. Core Audio APIs 技术笔记二(麦克风音量和增强设置)
  7. html设置鼠标移动变色,table行随鼠标移动变色示例
  8. 别人的底鼓/808为什么比你有力?你可能忘了用这个插件
  9. 大数据Hadoop快速入门教程
  10. VS下载插件慢的解决方法。