在查看zynq的clk时钟驱动时,在源码文件clkc.c中我们看到匹配属性字段”xlnx,ps7-clkc”,该字段匹配zynq-7000.dtsi的时钟子节点的compatible关键字属性相匹配,时钟的setup函数为zynq_clk_setup,查看整个源码包没有发现有调用该函数的痕迹,但是发现该函数被宏CLK_OF_DECLARE引用了。
首先看一下CLK_OF_DECLARE宏,它的定义位于“include/linux/clk-provider.h”中,负责在指定的section中(以__clk_of_table开始的位置),定义structof_device_id类型的变量,并由of_clk_init(在函数 zynq_timer_init (mach-zynq/common.c)中被调用)接口解析、匹配,如果匹配成功,则执行相应的回调函数(这里为of_fixed_clk_setup);初始化的时候,device tree负责读取DTS,并和这些变量的名字(这里为" xlnx,ps7-clkc ")匹配,如果匹配成功,则执行相应的回调函数(这里为of_fixed_clk_setup);of_fixed_clk_setup会解析两个DTS字段"clock-frequency"和"clock-output-names",然后调用clk_register_fixed_rate,注册clock。注意,注册时的flags为CLK_IS_ROOT,说明目前只支持ROOT类型的clock通过DTS注册;最后,调用of_clk_add_provider接口,将该clock添加到provider_list中,方便后续的查找使用。该接口会在后面再详细介绍。
在of_clk_init被调用之前,zynq_timer_init 先调用了zynq_clock_init函数,该函数实现功能如下:
1. 根据字段“compatible”匹配“xlnx,ps7-clkc”判断节点np是否存在
2. 判断np是否有地址内存定义
3. 判断np是否存在父节点slcr
4. 将节点np的物理地址赋值给全局变量zynq_clkc_base,该变量是void *指针,并通过__iomem修饰,强制定义链接区域
5. 通过of_node_put函数将np和slcr的refcount减1,我们查到of_node_put函数的说明,但我发现它调用了kobject_put,该函数简要说明如下:当一个kobject对象的引用ref被减少到0时,程序就会释放这个kobject相关的资源,所以在减少引用的函数中就应该有调用释放资源的相关代码,在下面内核代码中也可以看到。
下面我们正式来看下函数of_clk_init,该函数被调用时传递进来的参数matches为NULL,该函数具体实现了以下功能:
1. 初始化全局链表clk_provider_list,
2. 如果matches为空,就把__clk_of_table的地址赋值给matches,对应我们上面谈到的宏CLK_OF_DECLARE
3. 通过宏for_each_matching_node_and_match来捕获matches指向的of_device_id型指针数组中所有成员,当捕获数组成员时,执行操作如下:
3.1. 创造一个clock_provider对象
3.2. 把捕获道德数组成员的data和np指针分别赋值给clock_provider对象的clk_init_cb和np成员
3.3. 最后把clock_provider对象添加到全局链表clk_provider_list中
4. 判断如果clk_provider_list不为空,执行如下操作:
4.1. 遍历并去除该链表中的所有成员,并通过宏定义获取包含该成员的对象的指针,对象为clock_provider
4.2. 判断是否是否强制处理,一般我们都处理所有节点,然后判断clock_provider中np的父节点是否能使用,如果以上判断成立,执行以下操作:
4.2.1 通过调用clk_provider->clk_init_cb(clk_provider->np),初始化clock_provider中的时钟节点,具体函数为zynq_clk_setup,此处不做具体讨论
4.2.2 接着对父节点和子节点做时钟匹配(暂时不理解)
4.2.3 摧毁该节点和对象clock_provider
我在此处有一个疑问,为什么大费周章的去创造和销毁对象clock_provider,为什么不直接处理?希望有读者来解答一下。
接下来看到函数zynq_clk_setup,由上面看到我们把节点指针np传递了进去,具体实现功能如下:
1. 取出np中所有对象clock-output-names的数字中的字符串的指针,这些都是时钟的名字,在设备树文件zynq-7000.dtsi中被定义
2. 构建系统时钟树,具体如下,先看图:
由图中不难看出,ps_clk进来以后直接连接了3个时钟锁相环,分别是:ARM PLL、I/O PLL、 DDR PLL,其他所有的时钟如CPU时钟和外设时钟,都是从这几个模块中输出的,也就是为什么,会有代码cpu_parents[0] = clk_output_name[armpll]等的原因了,至于为什么有些时钟作为时钟源使用了2次,比如ARM PLL,这个时钟除了给CPU提供时钟以外,还给内部互联接口提供时钟,所以引用了2次;而I/O PLL不仅负责PS端的I/O设备,还负责PL部分I/O设备,所以也使用了2次。
3. 接着取出fclk-enable和ps-clk-frequency的32位整型值,其中ps_clk的频率为33.333333MHz,也可以从原理图来验证这一点
4. 通过clk_register_fixed_rate注册频率固定的时钟,此处注册了ps_clk,类型为CLK_IS_ROOT,作为根时钟,没有父节点,temp它的固有频率;并将注册结果生成clk 对象指针赋值给全局变量ps_clk,该时钟会被注册进全局链表clk_root_list中
5. 接下来注册3个时钟锁相环,这里xilinx实现了函数clk_register_zynq_pll,专门用于锁相环的注册,以下我们详细研究一下该函数,以此向下看:
5.1 函数在栈里面构造了类型为clk_init_data的对象initd,包含以下属性:1. 该锁相环的名字,2. 父节点的名字,3. 类型为clk_ops的结构体指针,4. 父节点的数量,5. 类型
5.2 构造了类型为 zynq_pll的对象pll,并对该对象进行了初始化
5.3 此处启动了pll自旋锁,配置了时钟寄存器,清除了该寄存器的PLL_BYPASS_QUAL位,此为在硬件启动时为1,起到关闭BYPASS功能使用的作用;再解锁自旋锁
5.4 通过函数clk_register注册并获取一个时钟对象指针,在该函数中会构建一个类型为clk_core对象core,获取initd的各个成员的值,所以最后initd已经没有存在的必必要了,所以直接在栈里面构造该对象,这2个PLL时钟会被提添加到他们父节点parent->children链表中
5.5 通过函数clk_register_mux注册有n个父节点的时钟,可以显实现以下的回调,get_parent/.set_parent/.recalc_rate,我们深入看一下这个函数最后会干嘛,进去看了下,和clk_register_zynq_pll类似,最后也是通过clk_register注册这个时钟
5.6 以此类推,注册了ddr pll和io pll
5.7 注册了cpu_mux,通过clk_register_divider注册这一类函数可以设置分频,通过.recalc_rate/.set_rate/.round_rate回调
5.8 通过clk_register_gate注册了CPU的时钟源,通过clk_register_gate注册的时钟只可以开关,通过.enable/.disable回调
5.9 通过clk_register_fixed_factor注册了CPU时钟的锁相环,这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调
5.10 以此类推,可以看到此处还使用了函数clk_prepare_enable,用来是能该时钟的相关功能操作
5.11 以此类推,注册了timer时钟、DDR时钟和Peripheral时钟,其中的函数zynq_clk_register_periph_clk只是把上面的各种时钟的注册方法进行了进一步的封装
... ...
5.12 函数统一检测所有注册的时钟是否有效
5.13 把时钟指针数组和数量交给全局变量clk_data
5.14 通过函数of_clk_add_provider把整个时钟注册进全局链表of_clk_providers,只要有全局变量就能被其他的函数简单的调用了
这些过程综合来说,就是就是从设备树文件,取出各种各样的时钟,按照芯片的时钟树结构,将他们组合起来,变得和上面的时钟框图“一样”,组成父子关系;接着把这些时钟的按照特性,通过的相关的函数组织起来,例如是否可以开关、是否集成锁相环等,防止以后对时钟进行不允许的操作,当然了需要立刻打开的时钟就直接打开了;最后把整个时钟树作为一个节点注册到全局链表of_clk_add_provider上面(如果没有注册全局变量,没法记录,也就没法调用了)。
评论
查看更多