当前位置: 首页 > 科技观察

一篇文章了解Linux时钟子系统的不同时钟设置_0

时间:2023-03-13 12:17:58 科技观察

需要来自一个或几个时钟源,最终形成一棵有枝有叶的时钟树。这个时钟树可以通过cat/sys/kernel/debug/clk/clk_summary查看。内核中使用CCF框架来管理时钟,如下图,右边是clockprovider,即ClockProvider;中间是CCF;左边是设备驱动的时钟使用者,即ClockConsumer。时钟提供者根节点一般为Oscillator(有源振荡器)或Crystal(无源振荡器)。中间节点有很多种,包括PLL(锁相环,用来升频)、Divider(分频器,用来降频)、Mux(从多个时钟路径中选择一个)、Gate(用来控制ON/离开)。叶节点是具有使用时钟作为输入的特定功能的硬件块。根据时钟的特性,时钟框架将时钟分为固定速率、门控、分频器、多路复用器、固定因子和复合时钟六类。以上六种数据结构本质上都是时钟设备。内核提取出这些时钟HW块的特征,用structclk_hw来表示,如下:structclk_hw{//指向CCF模块中对应的时钟设备实例structclk_core*core;//clk是访问clk_core的一个实例。每当消费者通过clk_get发起访问CCF中的时钟设备(即clk_core)时,都需要获取句柄,即clkstructclk*clk;//clockproviderdriver初始化时的数据,该数据用于初始化clk_hw对应的clk_core数据结构。conststructclk_init_data*init;};structclk_init_data{//时钟设备名称constchar*name;//时钟提供者驱动程序执行特定的硬件操作conststructclk_ops*ops;//描述clk_hw的拓扑结构constchar*const*parent_names;conststructclk_parent_data*parent_data;conststructclk_hw**parent_hws;u8num_parents;无符号长标志;};以固定频率振荡器fixedrate为例,其数据结构为:structclk_fixed_rate{//下面的Fixedrate是时钟设备的唯一成员structclk_hwhw;//基类unsignedlongfixed_rate;无符号长固定精度;u8标志;};其他具体的时钟设备大概也是这样,这里就不赘述了。下面用一张图来描述这些数据结构之间的关系:注册方式了解了数据结构之后,我们来看一下每种时钟设备的注册方式。1、固定速率时钟这类时钟频率固定,不能开关,不能调整频率,也不能选择父时钟。这是最简单的时钟类型。可以直接通过DTS配置来支持。也可以直接通过接口注册固定速率时钟,如下:CLK_OF_DECLARE(fixed_clk,"fixed-clock",of_fixed_clk_setup);structclk*clk_register_fixed_rate(structdevice*dev,constchar*name,constchar*parent_name,unsignedlongflags,unsignedlongfixed_rate);2.gateclock只能开启和关闭(会提供.enable/.disable回调),可以使用如下接口注册:structclk*clk_register_gate(structdevice*dev,constchar*name,constchar*parent_name,unsignedlongflags,void__iomem*reg,u8bit_idx,u8clk_gate_flags,spinlock_t*lock);3、dividerclock等时钟可以设置分频值(因此会提供.recalc_rate/.set_rate/.round_rate回调),可以通过以下两个接口注册:structclk*clk_register_divider(structdevice*dev,constchar*name,constchar*parent_name,unsignedlongflags,void__iomem*reg,u8shift,u8width,u8clk_divider_flags,spinlock_t*lock);structclk*clk_register_divider_table(结构设备*dev,constchar*name,constchar*parent_name,unsignedlongflags,void__iomem*reg,u8shift,u8width,u8clk_divider_flags,conststructclk_div_table*table,spinlock_t*lock);因为会实现.get_parent/.set_parent/.recalc_rate回调,所以可以通过以下两个接口注册:structclk*clk_register_mux(structdevice*dev,constchar*name,constchar**parent_names,u8num_parents,unsignedlongflags,void__iomem*reg,u8shift,u8width,u8clk_mux_flags,spinlock_t*lock);structclk*clk_register_mux_table(structdevice*dev,constchar*name,constchar**parent_names,u8num_parents,unsignedlong标志,void__iomem*reg,u8shift,u32mask,u8clk_mux_flags,u32*table,spinlock_t*lock);5.fixedfactorclock这种时钟有一个固定的factor(即倍频和分频),时钟的频率就是父时钟的频率,乘以dividebymulbydiv,多用于一些固定因子的时钟分频系数由于parentclock的频率可以改变,fixfactorclock的频率也可以改变,所以也提供了.recalc_rate/.set_rate/.round_rate等回调。可以通过以下接口注册:structclk*clk_register_fixed_factor(structdevice*dev,constchar*name,constchar*parent_name,unsignedlongflags,unsignedintmult,unsignedintdiv);6.Compositeclock,顾名思义,就是mux,divider,gate等时钟的组合,可以通过以下接口注册:structclk*clk_register_composite(structdevice*dev,constchar*name,constchar**parent_names,intnum_parents,structclk_hw*mux_hw,conststructclk_ops*mux_ops,structclk_hw*rate_hw,conststructclk_ops*rate_ops,structclk_hw*gate_hw,conststructclk_ops*gate_ops,unsignedlongflags);这些注册函数最终会通过函数clk_register注册到CommonClockFramework中,并作为structclk指针返回。如下图:然后将返回的structclk指针保存在一个数组中,调用of_clk_add_provider接口通知CommonClockFramework。ClockConsumer获取时钟,即通过时钟名称获取structclk指针的过程,通过clk_get、devm_clk_get、clk_get_sys、of_clk_get、of_clk_get_by_name、of_clk_get_from_provider等接口实现。这里,我们将clk_get作为一个举例分析其实现过程:structclk*clk_get(structdevice*dev,constchar*con_id){constchar*dev_id=dev?dev_name(dev):NULL;结构时钟*时钟;if(dev){//通过扫描“clock-names”中的所有值,并与传入的names进行比较,如果相同,则得到其索引(即“clock-names”中的数字),调用of_clk_get,获取时钟指针。clk=__of_clk_get_by_name(dev->of_node,dev_id,con_id);如果(!IS_ERR(clk)||PTR_ERR(clk)==-EPROBE_DEFER)返回clk;}returnclk_get_sys(dev_id,con_id);}structclk*of_clk_get(structdevice_node*np,intindex){structof_phandle_argsclkspec;结构时钟*时钟;国际电联;如果(索引<0)返回ERR_PTR(-EINVAL);rc=of_parse_phandle_with_args(np,“时钟”,“#clock-cells”,索引,&clkspec);如果(rc)返回ERR_PTR(rc);//获取时钟指针clk=of_clk_get_from_provider(&clkspec);of_node_put(clkspec.np);returnclk;}of_clk_get_from_provider通过方便的of_clk_providers链表,调用每个provider的get回调函数获取时钟指针。如下:structclk*of_clk_get_from_provider(structof_phandle_args*clkspec){structof_clk_provider*provider;结构时钟*clk=ERR_PTR(-ENOENT);/*检查我们的数组中是否有这样的提供者*/mutex_lock(&of_clk_lock);list_for_provider,&of_clk_providers,link){if(provider->node==clkspec->np)clk=provider->get(clkspec,provider->data);如果(!IS_ERR(clk))中断;}mutex_unlock(&of_clk_lock);returnclk;}至此,Consumer对应的就是Provider中提到的of_clk_add_provider。运行时钟//启动时钟前的准备工作/停止时钟后的善后工作。可以睡觉了intclk_prepare(structclk*clk)voidclk_unprepare(structclk*clk)//开始/停止时钟。不会睡觉。staticinlineintclk_enable(structclk*clk)staticinlinevoidclk_disable(structclk*clk)//时钟频率获取和设置staticinlineunsignedlongclk_get_rate(structclk*clk)staticinlineintclk_set_rate(structclk*clk,unsignedlongrate)staticinlinelongclk_round_rate(structclk*clk,unsignedlongrate)//获取/选择父时钟staticinlineintclk_set_parent(structclk*clk,structclk*parent)staticinlinestructclk*clk_get_parent(structclk*clk)//结合clk_prepare和clk_enable一起调用。结合clk_disable和clk_unprepare,调用staticinlineintclk_prepare_enable(structclk*clk)staticinlinevoidclk_disable_unprepare(structclk*clk)总结