admin 管理员组文章数量: 887021
linux 庐山培训,Linux
Clock统是Linux内核中专门管理时钟的子系统.
时钟在嵌入式系统中很重要,它就像人的脉搏一样,驱动器件工作.
任何一个CPU,都需要给它提供一个外部晶振,这个晶振就是用来提供时钟的;任何一个CPU内部的片上外设,也需要工作时钟:例如GPIO控制器,首先得给它提供工作时钟,然后才能访问它的寄存器.
如果你去看一个ARM CPU的芯片手册,你一定能找到一个章节,专门描述系统时钟,一般称之为时钟树(clock tree).
芯片手册从硬件的角度上描述了某个CPU的时钟系统是如何设计的,而Clock子系统从软件的层面来抽象这个设计.
在本章中,我们首先从硬件的角度来看看一个时钟树的的例子,然后自己思考一下软件层面该如何设计,最后看看clock子系统是怎么做的.
2.1时钟树
如今,可运行Linux的主流CPU,都有非常复杂的clock tree,我们随便拿一个处理器的spec,查看clock相关的章节,一定会有一个非常庞大和复杂的树状图.这个图由clock相关的器件,以及这些器件输出的clock组成.
下图是一个简单的示例:
clock相关的器件包括
用于产生clock的Oscillator(有源振荡器,也称作谐振荡器)或者Crystal(无源振荡器,也称晶振)
用于倍频的PLL(锁相环,Phase Locked Loop)
用于分频的divider
用于多路选择的Mux
用于clock enable控制的与门
使用clock的硬件模块(可称作consumer),例如HW1,它可能是GPIO控制器
器件用于产生具体的clock,例如osc_clk.这些器件的特性如下:
从输入(parent)和输出(children)的角度来看
某些器件没有parent,有一个或多个输出.例如osc_clk
某些器件有一个parent,有一个或多个输出.例如PLL1,它的parent是osc_clk,输出只有一个pll1_clk
某些器件有多个parent,有一个或多个输出.例如MUX,它有多个parent,输出只有一个hw3_clk
从频率的角度上来看
某些clock的频率是可以调整的,我们可以通过设置倍频和分频因子来调整输出频率.
这一类clock最常见.
某些clock的频率是固定的,而且不能开关,比如osc_clk.最常见的是24M或25M.
这一类clock称作fixed_rate_clock
某些clock的频率也是固定的,不过它可以开关.
这一类clock称作gate_clock
某些clock有固定的倍频和分频因子,它的输出频率跟随parent的变化而变化.
这一类clock称作fixed_factor_clock
在上图的时钟树中,有些是clock的提供者,我们可以称之为provider,例如oscillator, PLLs;有些是clock的使用者,我们可以称之为consumer,例如HW1, HW2, HW3.
在ARM CPU的内部,时钟树系统用来provide各种各样的时钟;各片上外设consume这些时钟.例如时钟树系统负责提供时钟给GPIO控制器, GPIO控制器则消费提供给它的工作时钟.
在设备驱动开发的过程中,我们经常会遇到的一个问题是:想要开启某个模块的时钟.
例如开发GPIO的驱动,在驱动的probe函数中,我们需要使能GPIO模块的工作时钟.
从软件层面,我们就是要提供一种机制,让consumer可以方便的获取/使能/配置/关闭一个时钟.
接下来,我们看看软件层面上该如何抽象.
2.2软件抽象
上一节我们介绍了时钟树,并介绍了时钟的provider和consumer.一个CPU芯片内部,会有很多个provider,也会有很多的consumer.软件层面需要做的事情就是管理所有这些provider,并向consumer提供尽量简单的接口使得consumer可以获取/使能/配置/关闭一个时钟.
因此,我们可以设计这样一个池子,所有的provider都可以向池子注册,把自己添加到池子里面.池子里面可以用一个链表把所有的provider都串起来,不同的provider以不同的name区分.当consumer需要获取某个clock的时候,通过name向池子查询即可.
在这个池子里面,每一个provider都可以抽象成一个独立的元素,因此我们最好设计一个数据结构,来表示每一个元素.
大致逻辑就是这样了, Linux内核的clock子系统基本上就是在干这些事情.
2.3clock子系统
Linux内核的clock子系统,按照其职能,可以大致分为3部分:
向下提供注册接口,以便各个clocks能注册进clock子系统
在核心层维护一个池子,管理所有注册进来的clocks.这一部分实现的是通用逻辑,与具体硬件无关.
向上,也就是像各个消费clocks的模块的device driver,提供获取/使能/配置/关闭clock的通用API
clock子系统的结构框图如下,一些细节你现在可能还不能理解.不过没关系,读完后面的章节你就会明白了.
后面的章节,我们也会分为上述3部分来描述,在阅读的过程中,你可以边看边对着下面这张图理解,这样会更加清晰.
2.clock provider—如何注册Clocks
3.1简介
前文我们介绍了时钟树,本章要阐述的主要问题就是如何把时钟树产生的这些clocks注册进Linux内核的clock子系统.换句话说,就是如何编写clock driver.
在ARM CPU内部,管理时钟树的也是一个单独的模块,一般叫PCM(ProgrammableClock Management),编写clock driver其实就是编写PCM的driver.
PCM也是CPU的一个片上外设,因此它也会借用platform这套机制.因此我们就需要有platform_device来描述设备,同时要有与之对应的platform_driver来控制设备.所谓控制,就写读写PCM的寄存器来使能/关闭时钟,设置时钟频率等等.在platform_driver的probe函数中,还有一项重要功能,就是调用clock子系统提供的API,向clock子系统注册.
下面,我看看编写clockdriver的大致步骤是怎样的.
3.2编写clock driver的大致步骤
由前文可知,首先你得准备一个platform_device,引入device tree的机制后, platform_device被dts替代了,因此我们就需要在dts里面描述时钟树.
编写platform_device(DTS node)
那么这个DTS该怎么写呢?通常有两种方式:
方式一:将时钟树中所有的clock,抽象为一个虚拟的设备,用一个DTS node表示.
1:/* arch/arm/boot/dts/exynos4210.dtsi */
2: theclock:clock–controller@0x10030000{
3:compatible= “samsung,exynos4210-clock”;
4:reg= <0x10030000 0x20000>;
5:#clock–cells= <1>;
6: };
这种方式跟编写一个普通的片上外设的DTS很类似,比如GPIO控制器的DTS,对比一下,是不是很类似.
从这个例子里面,我们可以看出一个clock的DTS node的基本语法:
compatible,决定了与这个node匹配的driver
reg就是用来描述PCM的寄存器
#clock-cells,这个是clock provider node独有的,表明在引用此clock时,需要用几个32位来描述.什么意思呢?
假设这个clock只有一个输出时钟,那么#clock–cells= <0>,我们在引用此clock的时候,只用指明此clock即可.
引用此clock是什么意思?引用指的是clock的consumer端.例如GPIO模块需要工作时钟,那么我们在编写GPIO的DTS node时,需要指明它的工作时钟是多少,这个过程就是引用,写个简单的例子:
gpio :gpio-controller@xxxx {
compatible=“yyyy”;
reg= ;
……
clocks = ; /*指明/引用某一个clock*/
}
假设这个clock有多个输出时钟,那么#clock–cells= <0>肯定不行,因为我们在引用此clock的时候,需要指明到底用哪一个输出时钟.
这个时候#clock–cells应该为 <1>,在引用此clock,就得这样写:
gpio :gpio-controller@xxxx {
compatible=“yyyy”;
reg= ;
……
clocks = ; /*指明/引用某一个clock, num是一个32位的整数,表明到底用哪一个输出clock*/
}
theclock,此DTS node的lable,clock consumer可以根据该名称引用clock
方式二:将时钟树中的每一个clock抽象为一个DTS node,并以树形的结构组织.这种方式相较与方式一,能更清晰的抽象出时钟的树形结构,从逻辑上也更合理,因此推荐大家使用这种方式.
举个方式二的例子:
1:/* arch/arm/boot/dts/sun4i-a10.dtsi */
2:clocks{
3:#address–cells= <1>;
4:#size–cells= <1>;
5:ranges;
19:osc24M:osc24M@01c20050{
20:#clock–cells= <0>;
21:compatible= “allwinner,sun4i-osc-clk”;
22:reg= <0x01c20050 0x4>;
23:clock–frequency= <24000000>;
24:};
25:
26:osc32k:osc32k{
27:#clock–cells= <0>;
28:compatible= “fixed-clock”;
29:clock–frequency= <32768>;
30:};
31:
32:pll1:pll1@01c20000{
33:#clock–cells= <0>;
34:compatible= “allwinner,sun4i-pll1-clk”;
35:reg= <0x01c20000 0x4>;
36:clocks= ;
37:};
38:
39:/* dummy is 200M */
40:cpu:cpu@01c20054{
41:#clock–cells= <0>;
42:compatible= “allwinner,sun4i-cpu-clk”;
43:reg= <0x01c20054 0x4>;
44:clocks= , , , ;
45:};
46:
47:axi:axi@01c20054{
48:#clock–cells= <0>;
49:compatible= “allwinner,sun4i-axi-clk”;
50:reg= <0x01c20054 0x4>;
51:clocks= ;
52:};
53:
54:axi_gates:axi_gates@01c2005c{
55:#clock–cells= <1>;
56:compatible= “allwinner,sun4i-axi-gates-clk”;
57:reg= <0x01c2005c 0x4>;
58:clocks= ;
59:clock–output–names= “axi_dram”;
60:};
61:
62:ahb:ahb@01c20054{
63:#clock–cells= <0>;
64:compatible= “allwinner,sun4i-ahb-clk”;
65:reg= <0x01c20054 0x4>;
66:clocks= ;
67:};
68:
69:ahb_gates:ahb_gates@01c20060{
70:#clock–cells= <1>;
71:compatible= “allwinner,sun4i-ahb-gates-clk”;
72:reg= <0x01c20060 0x8>;
73:clocks= ;
74:clock–output–names= “ahb_usb0”, “ahb_ehci0”,
75:“ahb_ohci0”, “ahb_ehci1”, “ahb_ohci1”, “ahb_ss”,
76:“ahb_dma”, “ahb_bist”, “ahb_mmc0”, “ahb_mmc1”,
77:“ahb_mmc2”, “ahb_mmc3”, “ahb_ms”, “ahb_nand”,
78:“ahb_sdram”, “ahb_ace”,“ahb_emac”, “ahb_ts”,
79:“ahb_spi0”, “ahb_spi1”, “ahb_spi2”, “ahb_spi3”,
80:“ahb_pata”, “ahb_sata”, “ahb_gps”, “ahb_ve”,
81:“ahb_tvd”, “ahb_tve0”, “ahb_tve1”, “ahb_lcd0”,
82:“ahb_lcd1”, “ahb_csi0”, “ahb_csi1”, “ahb_hdmi”,
83:“ahb_de_be0”, “ahb_de_be1”, “ahb_de_fe0”,
84:“ahb_de_fe1”, “ahb_mp”, “ahb_mali400”;
85:};
86:
87:apb0:apb0@01c20054{
88:#clock–cells= <0>;
89:compatible= “allwinner,sun4i-apb0-clk”;
90:reg= <0x01c20054 0x4>;
91:clocks= ;
92:};
93:
94:apb0_gates:apb0_gates@01c20068{
95:#clock–cells= <1>;
96:compatible= “allwinner,sun4i-apb0-gates-clk”;
97:reg= <0x01c20068 0x4>;
98:clocks= ;
99:clock–output–names= “apb0_codec”, “apb0_spdif”,
100:“apb0_ac97”, “apb0_iis”, “apb0_pio”, “apb0_ir0”,
101:“apb0_ir1”, “apb0_keypad”;
102:};
103:
104:/* dummy is pll62 */
105:apb1_mux:apb1_mux@01c20058{
106:#clock–cells= <0>;
107:compatible= “allwinner,sun4i-apb1-mux-clk”;
108:reg= <0x01c20058 0x4>;
109:clocks= , , ;
110:};
111:
112:apb1:apb1@01c20058{
113:#clock–cells= <0>;
114:compatible= “allwinner,sun4i-apb1-clk”;
115:reg= <0x01c20058 0x4>;
116:clocks= ;
117:};
118:
119:apb1_gates:apb1_gates@01c2006c{
120:#clock–cells= <1>;
121:compatible= “allwinner,sun4i-apb1-gates-clk”;
122:reg= <0x01c2006c 0x4>;
123:clocks= ;
124:clock–output–names= “apb1_i2c0”, “apb1_i2c1”,
125:“apb1_i2c2”, “apb1_can”, “apb1_scr”,
126:“apb1_ps20”, “apb1_ps21”, “apb1_uart0”,
127:“apb1_uart1”, “apb1_uart2”, “apb1_uart3”,
128:“apb1_uart4”, “apb1_uart5”, “apb1_uart6”,
129:“apb1_uart7”;
130:};
131: };
osc24M 代表24M的晶振
osc32k 代表32k的慢速时钟
pll1,它的父时钟是osc24M,只有一个输出时钟
cpu,它的父时钟有多个, , , ,只有一个输出时钟
ahb_gates,它的父时钟只有一个,是ahb,但是它的输出时钟有很多个
……后面的都类似,不一一说明了
看见了吧,这种方式确实能清晰的描述多个clocks的树形关系.
编写platform_driver
有了platform_device之后,接下来就得编写platform_driver,在driver里面最重要的事情就是向clock子系统注册.
如何注册呢?
clock子系统定义了clock driver需要实现的数据结构,同时提供了注册的函数.我们只需要准备好相关的数据结构,然后调用注册函数进行注册即可.
这些数据结构和接口函数的定义是在:include/linux/clk-provider.h
需要实现的数据结构是 struct clk_hw,需要调用的注册函数是struct clk *clk_register(struct device *dev, struct clk_hw *hw).数据结构和注册函数的细节我们在后文说明.
注册函数会返回给你一个struct clk类型的指针,在Linux的clock子系统中,用一个struct clk代表一个clock.你的CPU的时钟树里有多少个clock,就会有多少个对应的struct clk.
当你拿到返回结果之后,你需要调用另外一个API :of_clk_add_provider,把刚刚拿到的返回结果通过此API丢到池子里面,好让consumer从这个池子里获取某一个clock.
池子的概念我们在前文讲述过,除了上述方法,你还可以用另外一种方法把struct clk添加到池子里面.
如果你要用这种方法,你会用到clock子系统提供给你的另外几个API,这些API的定义在:include/linux/clkdev.h
其中最主要的一个API是int clk_register_clkdev(struct clk *, const char *, const char *, …),你可以在你的clock driver里面调用这个API,把刚刚拿到的返回结果通过此API丢到池子里面.
为什么会存在这两种方式呢?得从consumer的角度来解答这个问题.
我们用GPIO来举个例子, GPIO控制器需要工作时钟,这个时钟假设叫gpio_clk,它是一个provider.你需要把这个provider注册进clock子系统,并把用于描述这个gpio_clk的struct clk添加到池子里面.
在GPIO控制器的driver代码里,我们需要获取到gpio_clk这个时钟并使能它,获取的过程就是向池子查询.
怎么查询?你可以直接给定一个name,然后通过这个name向池子查询;你也可以在GPIO的DTS node里面用clocks = ;方式指明使用哪一个clock,然后通过这种方式向池子查询.
如果consumer是通过name查询,则对应的添加到池子的API就是clk_register_clkdev
如果consumer是通过DTS查询,则对应的添加到池子的API就是of_clk_add_provider
那么我在我的clock driver里面到底应该用哪个API向池子添加clk呢?
两者你都应该同时使用,这样consumer端不管用哪种查询方式都能工作.
读到这里,建议你回头看看clock子系统的系统框图,结合框图在琢磨琢磨.
接下来,我们就会详细介绍这些数据结构和相关的API了.
3.3主要数据结构
通过前文,我们知道了clock driver需要实现的一个主要的数据结构struct clk_hw.
与之相关的还有另外几个重要数据结构:struct clk_init_data和struct clk_ops.
下面我们看看这几个数据结构.
structclk_hw
头文件: include/linux/clk-provider.h
struct clk_hw
Comment
struct clk_core *core
clk_core是clock子系统核心层的一个数据结构,由核心层代码创建和维护,一个clk_core对应一个具体的clock.
struct clk *clk
clk也是clock子系统核心层的一个数据结构,同样由核心层代码创建和维护,它也对应一个具体的clock.
clk_core和clk的细节,放在《clock core》一章中描述
const struct clk_init_data *init
clk_init_data是provider需要实现并填充的一个数据结构,编写clock driver,最主要的任务就是实现它
structclk_init_data
头文件: include/linux/clk-provider.h
structclk_init_data
Comment
const char*name
该clock的name,系统中会存在很多个clock,每一个clock都会有一个名称,可以用名称来区分不同的clock,因此不能重名
const struct clk_ops*ops
与clock控制相关的ops,例如enable/disable; set_rate等等.
当consumer端想要操作某个clock时,最终就会调用到该clock的clk_ops
const char**parent_names
该clock的所有parents.通过name,就能找到parent是谁了
u8num_parents
一个clock可能有多个parents, num_parents指明到底有几个
unsigned longflags
标志位,表明该clock的一些特性,核心层代码会根据不同的flags采取不同的动作.可选的值如下:
#defineCLK_SET_RATE_GATEBIT(0) /* must be gated across rate change */
#defineCLK_SET_PARENT_GATEBIT(1) /* must be gated across re-parent */
#defineCLK_SET_RATE_PARENTBIT(2) /* propagate rate change up one level */
#defineCLK_IGNORE_UNUSEDBIT(3) /* do not gate even if unused */
#defineCLK_IS_ROOTBIT(4) /* root clk, has no parent */
#defineCLK_IS_BASICBIT(5) /* Basic clk, can’t do a to_clk_foo() */
#defineCLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */
#defineCLK_SET_RATE_NO_REPARENTBIT(7) /* don’t re-parent on rate change */
#defineCLK_GET_ACCURACY_NOCACHEBIT(8) /* do not use the cached clk accuracy */
structclk_ops
头文件: include/linux/clk-provider.h
下述这些ops在.h文件里面都有详细的注释,可以阅读源代码获取更多信息.
structclk_ops
Comment
int(*prepare)(struct clk_hw *hw)
为什么要有prepare接口,直接enable不就行了吗?
从硬件的角度来说,某些clock,如果想使能它,需要等待一段时间.
例如倍频器PLL,当你使能倍频器之后,你需要等待几毫秒让倍频器工作平稳.
因为要等待,软件上就有可能sleep,也就是休眠.但是Linux内核中有很多情况下不能休眠,比如说中断服务器程序.
如果你把所有的操作都放在enable这一个函数里面,那么enable函数就可能休眠,因而中断服务程序里面就不能调用enable函数.
但实际情况是,很多时候,我们都要求在中断服务程序里面开/关某个clock
怎么办呢?
拆分成2个函数, prepare和enable.
prepare负责使能clock之前的准备工作, prepare里面可以休眠,一旦prepare返回,就意味着clock已经完全准备好了,可以直接开/关
enable负责打开clock,它不能休眠,这样在中断服务程序中也可以调用enable了
void(*unprepare)(struct clk_hw *hw)
prepare的反函数, un-do prepare里面做的所有事情
int(*is_prepared)(struct clk_hw *hw)
is_prepared,判断clock是否已经prepared,可以不提供.
clock framework core会维护一个prepare的计数(该计数在clk_prepare调用时加一,在clk_unprepare时减一),并依据该计数判断是否prepared
void(*unprepare_unused)(struct clk_hw *hw)
自动unprepare unused clocks
clock framework core提供一个clk_disable_unused接口,在系统初始化的late_call中调用,用于关闭unused clocks,这个接口会调用相应clock的.unprepare_unused和.disable_unused函数
int(*enable)(struct clk_hw *hw)
使能clock,此函数不能休眠
当此函数返回时,代表consumer端可以收到一个稳定,可用的clock波形了.
void(*disable)(struct clk_hw *hw)
禁止clock,此函数不能休眠
int(*is_enabled)(struct clk_hw *hw)
与is_prepared类似
void(*disable_unused)(struct clk_hw *hw)
与unprepare_unused类似
int(*save_context)(struct clk_hw *hw)
Save the context of the clock in prepration for poweroff
void(*restore_context)(struct clk_hw *hw)
Restore the context of the clock after a restoration of power
unsigned long(*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate)
以parent clock rate为参数,从新计算并返回clock rate
long(*round_rate)(structclk_hw *hw, unsigned long rate, unsigned long *parent_rate)
Given a target rate asinput, returns the closest rate actually supported by the clock
该接口有点特别,在返回rounded rate的同时,会通过一个指针,返回round后parent的rate.这和CLK_SET_RATE_PARENT flag有关
当clock consumer调用clk_round_rate获取一个近似的rate时,如果该clock没有提供.round_rate函数,有两种方法:
在没有设置CLK_SET_RATE_PARENT标志时,直接返回该clock的cache rate
如果设置了CLK_SET_RATE_PARENT标志,则会询问parent,即调用clk_round_rate获取parent clock能提供的、最接近该rate的值.
这是什么意思呢?也就是说,如果parent clock可以得到一个近似的rate值,那么通过改变parent clock,就能得到所需的clock
long(*determine_rate)(struct clk_hw *hw,
unsigned long rate,
unsigned long min_rate,
unsigned long max_rate,
unsigned long *best_parent_rate,
struct clk_hw **best_parent_hw)
与round_rate类似,暂时不清楚它俩有什么区别.
int(*set_parent)(struct clk_hw *hw, u8 index)
Change the input source of this clock
有的clocks可能有多个parents,那到底用哪一个呢?由参数index决定
u8(*get_parent)(struct clk_hw *hw)
Queries the hardware to determine the parent of a clock
返回值是一个u8类型的变量,它是一个index,通过它可以查找到对应的parent.所有的parents都存储在.parent_names or .parents arrays里面
实际上,此函数会读取硬件寄存器,然后把寄存器的值转变为对应的index
int(*set_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
Change the rate of this clock
The requested rate is specified by the second argument, which should typically be the return of .round_rate call
The third argument gives the parent rate which is likely helpful for most .set_rate implementation
int (*set_rate_and_parent)(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate,
u8 index)
Change the rate and the parent of this clock
This callback is optional (and unnecessary) for clocks with 0 or 1 parents as well as for clocks that can tolerate switching the rate and the parent separately via calls to .set_parent and .set_rate
unsigned long (*recalc_accuracy)(struct clk_hw *hw,
unsigned long parent_accuracy)
Recalculate the accuracy of this clock
The clock accuracy is expressed in ppb (parts per billion)
int(*get_phase)(struct clk_hw *hw)
Queries the hardware to get the current phase of a clock
Returned values are 0-359 degrees on success, negative error codes on failure
int(*set_phase)(structclk_hw *hw, int degrees)
Shift the phase this clock signal in degrees specified by the secondargument
Valid values for degrees are 0-359
Return 0 on success, otherwise -EERROR
void(*init)(struct clk_hw *hw)
Perform platform-specific initialization magic
不过从代码注释来看,内核推荐你不要实现这个接口函数,后面可能会遗弃
int(*debug_init)(struct clk_hw *hw, struct dentry *dentry)
debugfs相关,这里不细述
细节可以看代码注释
3.4主要API说明
Linux clock子系统向下提供几个重要的API,下面我挨个看下这些API的细节.
clk_register/devm_clk_register
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk.c
/**
* clk_register – allocate a new clock, register it and return an opaque cookie
* @dev: device that is registering this clock
* @hw: link to hardware-specific clock data
*
*clk_registeris the primary interface for populating the clock tree with new
*clocknodes. It returns a pointer to the newly allocated struct clk which
* cannot be dereferenced by driver code but may be used in conjuction with the
*restof the clock API. In the event of an error clk_register will return an
*errorcode; drivers must test for an error code after calling clk_register.
*/
structclk*clk_register(structdevice*dev, structclk_hw*hw);
structclk*devm_clk_register(structdevice*dev, structclk_hw*hw);
voidclk_unregister(structclk*clk);
voiddevm_clk_unregister(structdevice*dev, structclk*clk);
clk_register是clock子系统提供的注册clock的最基础的API函数,后文描述的其它APIs都是对它的封装.
devm_clk_register是clk_register的devm版本, devm机制在《设备模型》一文中有详述.
要向系统注册一个clock也很简单,准备好clk_hw结构体,然后调用clk_register接口即可.
不过,clock framework所做的远比这周到,它基于clk_register,又封装了其它接口,在向clock子系统注册时,连struct clk_hw都不需要关心,而是直接使用类似人类语言的方式.
也就是说,实际在编写clock driver的时候,我们不会直接使用clk_register接口,只需要调用下面的某个API即可.
下文我们一一介绍这些API.
clk_register_fixed_rate
clock有不同的类型,有的clock频率是固定的,不可调整的,例如外部晶振,频率就是固定的(24M / 25M).这种类型的clock就是fixed_rate clock.
fixed_rateclock具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类clock.
如果要注册这种类型的clock,直接调用本API,传递相应的参数给本API即可. API的细节如下:
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk-fixed-rate.c
/*
* DOC: Basic clock implementations common to many platforms
*
* Each basic clock hardware type is comprised of a structure describing the
*clockhardware, implementations of the relevant callbacks in struct clk_ops,
*uniqueflags for that hardware type, a registration function and an
*alternativemacro for static initialization
*/
/**
*structclk_fixed_rate – fixed-rate clock
* @hw: handle between common and hardware-specific interfaces
* @fixed_rate: constant frequency of clock
*/
structclk_fixed_rate{
structclk_hw hw;
unsigned longfixed_rate;
unsigned longfixed_accuracy;
u8flags;
};
extern const structclk_ops clk_fixed_rate_ops;
structclk*clk_register_fixed_rate(structdevice*dev, const char *name,
const char *parent_name, unsigned longflags,
unsigned longfixed_rate);
structclk*clk_register_fixed_rate_with_accuracy(structdevice*dev,
const char *name, const char *parent_name, unsigned longflags,
unsigned longfixed_rate, unsigned longfixed_accuracy);
voidof_fixed_clk_setup(structdevice_node*np);
若想注册一个fixed rate clock,除了你可以在自己的clock driver里面手动调用clk_register_fixed_rate之外,还有一种更简单的方式,那就是用DTS.
你可以在DTS里面用如下方式描述一个fixed rate clock:
26:osc32k:osc32k{
27:#clock–cells= <0>;
28:compatible = “fixed-clock“;
29:clock–frequency = <32768>;
clock-accuracy= xxxx;
clock-output-names= xxxx;
30:};
关键地方在于compatible一定要是”fixed-clock”
“drivers/clk/clk-fixed-rate.c”中的of_fixed_clk_setup会负责匹配这个compatible,这个C文件是clock子系统实现的.
of_fixed_clk_setup会解析3个参数:clock-frequency,clock-accuracy,clock-output-names
clock-frequency是必须的,另外2个参数可选.
参数解析完毕之后,会调用clk_register_fixed_rate_with_accuracy向系统注册一个clock.
clk_register_gate
这一类clock只可开关(会提供.enable/.disable回调),可使用下面接口注册:
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk-gate.c
/**
*structclk_gate – gating clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling gate
* @bit_idx: single bit controlling gate
* @flags: hardware-specific flags
* @lock: register lock
*
* Clock which can gate its output. Implements .enable & .disable
*
* Flags:
* CLK_GATE_SET_TO_DISABLE – by default this clock sets the bit at bit_idx to
* enablethe clock. Setting this flag does the opposite: setting the bit
* disablethe clock and clearing it enables the clock
* CLK_GATE_HIWORD_MASK – The gate settings are only inlower16-bit
* ofthis register, and mask of gate bits are in higher 16-bit of this
* register. While setting the gate bits, higher 16-bit should also be
* updatedto indicate changing gate bits.
*/
structclk_gate{
structclk_hw hw;
void__iomem*reg;
u8 bit_idx;
u8flags;
spinlock_t*lock;
};
#define CLK_GATE_SET_TO_DISABLEBIT(0)
#define CLK_GATE_HIWORD_MASKBIT(1)
extern const structclk_ops clk_gate_ops;
structclk*clk_register_gate(structdevice*dev, const char *name,
const char *parent_name, unsigned longflags,
void__iomem*reg,u8 bit_idx,
u8 clk_gate_flags,spinlock_t*lock);
voidclk_unregister_gate(structclk*clk);
clk_register_gate,它的参数列表如下:
name:clock的名称
parent_name:parent clock的名称,没有的话可留空
flags:参考3.3节 struct clk_hw结构体中对于flags的描述
reg:控制该clock开关的寄存器地址(虚拟地址)
bit_idx: reg中,第几个bit是控制clock开/关的
clk_gate_flags: gate clock特有的参数.其中一个可选值是CLK_GATE_SET_TO_DISABLE,它的意思是1表示开还是0表示开,类似于翻转位.
lock:如果clock开关时需要互斥,可提供一个spinlock.
clock子系统并没有定义类似fixed rate clock的DTS处理方式.
如果你想借用DTS,也很简单.如下:
ehrpwm1_tbclk:ehrpwm1_tbclk@44e10664{
#clock-cells = <0>;
compatible = “ti,gate-clock”;
clocks = ;
ti,bit–shift= <1>;
reg = <0x0664>;
};
注意它的compatible,自己实现一个driver,匹配这个compatible.然后在driver里面解析DTS相关参数并调用clk_register_gateAPI即可.
clk_register_divider/clk_register_divider_table
这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调),可通过下面两个接口注册:
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk-divider.c
/**
*structclk_divider – adjustable divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register containing the divider
* @shift: shift to the divider bit field
* @width: width of the divider bit field
* @table: array of value/divider pairs, last entry should have div = 0
* @lock: register lock
*
* Clock with an adjustable divider affecting its output frequency. Implements
* .recalc_rate, .set_rate and .round_rate
*
* Flags:
* CLK_DIVIDER_ONE_BASED – by default the divisor is the value read from the
* registerplus one. If CLK_DIVIDER_ONE_BASED is set then the divider is
* theraw value read from the register, with the value of zero considered
* invalid, unless CLK_DIVIDER_ALLOW_ZERO is set.
* CLK_DIVIDER_POWER_OF_TWO – clock divisor is 2 raised to the value read from
* thehardware register
* CLK_DIVIDER_ALLOW_ZERO – Allow zero divisors. For dividers which have
* CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor.
* Somehardware implementations gracefully handle this case and allow a
* zerodivisor by not modifying their input clock
* (divide by one / bypass).
* CLK_DIVIDER_HIWORD_MASK – The divider settings are only inlower16-bit
* ofthis register, and mask of divider bits are in higher 16-bit of this
* register. While setting the divider bits, higher 16-bit should also be
* updatedto indicate changing divider bits.
* CLK_DIVIDER_ROUND_CLOSEST – Makes the best calculated divider to be rounded
* tothe closest integer instead of the up one.
* CLK_DIVIDER_READ_ONLY – The divider settings are preconfigured and should
* notbe changed by the clock framework.
*/
structclk_divider{
structclk_hw hw;
void__iomem*reg;
u8shift;
u8width;
u8flags;
const structclk_div_table*table;
spinlock_t*lock;
u32context;
};
#define CLK_DIVIDER_ONE_BASEDBIT(0)
#define CLK_DIVIDER_POWER_OF_TWOBIT(1)
#define CLK_DIVIDER_ALLOW_ZEROBIT(2)
#define CLK_DIVIDER_HIWORD_MASKBIT(3)
#define CLK_DIVIDER_ROUND_CLOSESTBIT(4)
#define CLK_DIVIDER_READ_ONLYBIT(5)
extern const structclk_ops clk_divider_ops;
unsigned longdivider_recalc_rate(structclk_hw*hw, unsigned longparent_rate,
unsigned intval, const structclk_div_table*table,
unsigned longflags);
longdivider_round_rate(structclk_hw*hw, unsigned longrate,
unsigned long *prate, const structclk_div_table*table,
u8width, unsigned longflags);
intdivider_get_val(unsigned longrate, unsigned longparent_rate,
const structclk_div_table*table,u8 width,
unsigned longflags);
structclk*clk_register_divider(structdevice*dev, const char *name,
const char *parent_name, unsigned longflags,
void__iomem*reg,u8 shift,u8 width,
u8 clk_divider_flags,spinlock_t*lock);
structclk*clk_register_divider_table(structdevice*dev, const char *name,
const char *parent_name, unsigned longflags,
void__iomem*reg,u8 shift,u8 width,
u8 clk_divider_flags, const structclk_div_table*table,
spinlock_t*lock);
voidclk_unregister_divider(structclk*clk);
clk_register_divider:该接口用于注册分频比规则的clock
reg:控制clock分频比的寄存器
shift:控制分频比的bit在寄存器中的偏移
width:控制分频比的bit位数,默认情况下,实际的divider值是寄存器值加1.
如果有其它例外,可使用下面的的flag指示。
clk_divider_flags:divider clock特有的flag,包括:
CLK_DIVIDER_ONE_BASED:
实际的divider值就是寄存器值(0是无效的,除非设置CLK_DIVIDER_ALLOW_ZERO flag)
CLK_DIVIDER_POWER_OF_TWO:
实际的divider值是寄存器值得2次方
CLK_DIVIDER_ALLOW_ZERO:
divider值可以为0(不改变,视硬件支持而定)
clk_register_divider_table:该接口用于注册分频比不规则的clock,和上面接口比较,差别在于divider值和寄存器值得对应关系由一个table决定. table的原型如下:
structclk_div_table{
unsigned intval;
unsigned intdiv;
};
val代表寄存器值, div代表对应的分频值.
同样, clock子系统并没有实现DTS相关接口,不过你可以自己编写driver去解析DTS并调用API注册.
clk_register_mux
这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调,可通过下面两个接口注册:
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk-mux.c
/**
*structclk_mux – multiplexer clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling multiplexer
* @shift: shift to multiplexer bit field
* @width: width of mutliplexer bit field
* @flags: hardware-specific flags
* @lock: register lock
*
* Clock with multiple selectable parents. Implements .get_parent, .set_parent
*and.recalc_rate
*
* Flags:
* CLK_MUX_INDEX_ONE – register index starts at 1, not 0
* CLK_MUX_INDEX_BIT – register index is a single bit (power of two)
* CLK_MUX_HIWORD_MASK – The mux settings are only inlower16-bit of this
* register, and mask of mux bits are in higher 16-bit of this register.
* Whilesetting the mux bits, higher 16-bit should also be updated to
* indicatechanging mux bits.
* CLK_MUX_ROUND_CLOSEST – Use the parent rate that is closest to the desired
* frequency.
*/
structclk_mux{
structclk_hw hw;
void__iomem*reg;
u32*table;
u32mask;
u8shift;
u8flags;
spinlock_t*lock;
u8 saved_parent;
};
#define CLK_MUX_INDEX_ONEBIT(0)
#define CLK_MUX_INDEX_BITBIT(1)
#define CLK_MUX_HIWORD_MASKBIT(2)
#define CLK_MUX_READ_ONLYBIT(3)/* mux can’t be changed */
#define CLK_MUX_ROUND_CLOSESTBIT(4)
extern const structclk_ops clk_mux_ops;
extern const structclk_ops clk_mux_ro_ops;
structclk*clk_register_mux(structdevice*dev, const char *name,
const char **parent_names,u8 num_parents, unsigned longflags,
void__iomem*reg,u8 shift,u8 width,
u8 clk_mux_flags,spinlock_t*lock);
structclk*clk_register_mux_table(structdevice*dev, const char *name,
const char **parent_names,u8 num_parents, unsigned longflags,
void__iomem*reg,u8 shift,u32 mask,
u8 clk_mux_flags,u32*table,spinlock_t*lock);
voidclk_unregister_mux(structclk*clk);
clk_register_mux:该接口可注册mux控制比较规则的clock(类似divider clock)
parent_names:一个字符串数组,用于描述所有可能的parent clock
num_parents:parent clock的个数
reg、shift、width:选择parent的寄存器、偏移、宽度
clk_mux_flags:mux clock特有的flag
CLK_MUX_INDEX_ONE:寄存器值不是从0开始,而是从1开始
CLK_MUX_INDEX_BIT:寄存器值为2的幂
clk_register_mux_table:该接口通过一个table,注册mux控制不规则的clock,原理和divider clock类似,不再详细介绍
同样, clock子系统并没有实现DTS相关接口,不过你可以自己编写driver去解析DTS并调用API注册.
clk_register_fixed_factor
这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock.
由于parent clock的频率可以改变,因而fix factor clock也可以改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate等回调.
可通过下面接口注册:
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk-fixed-factor.c
void of_fixed_factor_clk_setup(structdevice_node*node);
/**
*structclk_fixed_factor – fixed multiplier and divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @mult: multiplier
* @div: divider
*
* Clock with a fixed multiplier and divider. The output frequency is the
*parentclock rate divided by div and multiplied by mult.
* Implements .recalc_rate, .set_rate and .round_rate
*/
structclk_fixed_factor{
structclk_hw hw;
unsigned intmult;
unsigned intdiv;
};
extern structclk_ops clk_fixed_factor_ops;
structclk*clk_register_fixed_factor(structdevice*dev, const char *name,
const char *parent_name, unsigned longflags,
unsigned intmult, unsigned intdiv);
API参数比较简单,不多说了.
另外, clock子系统还提供了此种类型clock的DTS接口.
clk-fixed-factor.c中的of_fixed_factor_clk_setup函数会负责解析DTS.关于DTS的匹配规则和相关参数,自己看看源码吧.
clk_register_fractional_divider
这一类和divider clock很像,唯一的不同在于它可支持到更细的粒度 (可用小数表示分频因子) .例如 clk = parent / 1.5
API说明如下:
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk-fractional-divider.c
/**
*structclk_fractional_divider – adjustable fractional divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register containing the divider
* @mshift: shift to the numerator bit field
* @mwidth: width of the numerator bit field
* @nshift: shift to the denominator bit field
* @nwidth: width of the denominator bit field
* @lock: register lock
*
* Clock with adjustable fractional divider affecting its output frequency.
*/
structclk_fractional_divider{
structclk_hw hw;
void__iomem*reg;
u8mshift;
u32mmask;
u8nshift;
u32nmask;
u8flags;
spinlock_t*lock;
};
extern const structclk_ops clk_fractional_divider_ops;
structclk*clk_register_fractional_divider(structdevice*dev,
const char *name, const char *parent_name, unsigned longflags,
void__iomem*reg,u8 mshift,u8 mwidth,u8 nshift,u8 nwidth,
u8 clk_divider_flags,spinlock_t*lock);
clock子系统没有提供相关的DTS解析函数.
clk_register_composite
顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk-composite.c
/***
*structclk_composite – aggregate clock of mux, divider and gate clocks
*
* @hw: handle between common and hardware-specific interfaces
* @mux_hw: handle between composite and hardware-specific mux clock
* @rate_hw: handle between composite and hardware-specific rate clock
* @gate_hw: handle between composite and hardware-specific gate clock
* @mux_ops: clock ops for mux
* @rate_ops: clock ops for rate
* @gate_ops: clock ops for gate
*/
structclk_composite{
structclk_hw hw;
structclk_ops ops;
structclk_hw*mux_hw;
structclk_hw*rate_hw;
structclk_hw*gate_hw;
const structclk_ops*mux_ops;
const structclk_ops*rate_ops;
const structclk_ops*gate_ops;
};
structclk*clk_register_composite(structdevice*dev, const char *name,
const char **parent_names, intnum_parents,
structclk_hw*mux_hw, const structclk_ops*mux_ops,
structclk_hw*rate_hw, const structclk_ops*rate_ops,
structclk_hw*gate_hw, const structclk_ops*gate_ops,
unsigned longflags);
看着有点复杂,但理解了上面1~5类clock,这里就只剩下苦力了,耐心一点,就可以了.
另外, clock子系统没有提供相关的DTS解析函数.
clk_register_gpio_gate
把某个gpio当做一个gate clock.也就是说可以通过clock子系统来控制这个gpio开/关,也就是控制这个GPIO输出高/低电平.
某些情况下,有的模块需要通过某个GPIO来控制其使能/禁止,例如蓝牙模块.而且你也不需要向此模块提供时钟,模板本身就有晶振存在,可以自己给自己供时钟.
这个时候我们就可以把该GPIO抽象成gpio gate clock,借助clock子系统,来控制模块的enable/disable.
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk-gpio-gate.c
/***
*structclk_gpio_gate – gpio gated clock
*
* @hw: handle between common and hardware-specific interfaces
* @gpiod: gpio descriptor
*
* Clock with a gpio control for enabling and disabling the parent clock.
* Implements .enable, .disable and .is_enabled
*/
structclk_gpio{
structclk_hw hw;
structgpio_desc*gpiod;
};
extern const structclk_ops clk_gpio_gate_ops;
structclk*clk_register_gpio_gate(structdevice*dev, const char *name,
const char *parent_name, unsignedgpio, boolactive_low,
unsigned longflags);
void of_gpio_clk_gate_setup(structdevice_node*node);
API参数比较简单,不多说了.
另外, clock子系统还提供了此种类型clock的DTS接口.
xxx_unregister
上述xxx_register函数都有对应的xxx_unregister.
unregister的函数原型在上述代码中都有提及,这里不多说了,有兴趣可以自行阅读源代码.
clk_register_clkdev
头文件: include/linux/clkdev.h
实现文件: drivers/clk/clkdev.c
原型:intclk_register_clkdev(structclk*, const char *, const char *, …);
上述的clk_register_xxx接口会向clock子系统注册一个clock,并返回一个代表该clock的struct clk结构体.
clk_register_clkdev的作用就是把返回的这个struct clk添加到某个池子里面.
这个池子其实就是个链表啦,链表定义在clkdev.c里面.
池子里面主要是用name做为关键字,区分不同的clk.
当consumer端想要查询某个clk的时候,就会尝试从这个链表里面通过clock name去检索.
of_clk_add_provider
头文件: include/linux/clk-provider.h
实现文件: drivers/clk/clk.c
原型:
intof_clk_add_provider(structdevice_node*np,
structclk*(*clk_src_get)(structof_phandle_args*args,
void *data),
void *data);
此API的主要目的也是把得到的struct clk结构体添加到某个池子里面.
这个池子也是个链表,定义在clk.c里面.
与上面那个池子的不同之处在于,这里是用device_node做为关键字来区分不同的clk.
当consumer端想要查询某个clk的时候,会在DTS node里面通过clocks = 来引用某个clock.
通过引用的这个clock的phandle,就能找到对应的device_node.然后通过device_node就能从池子里面检索出需要的clk.
其实,这两种池子对应了我们的consumer端的两种方式:一种是通过name获取clk,不需要DTS;另外一种就是通过DTS.
随着Linux内核大力推行DTS,方式二会逐渐成为主流.
3.clockcore —如何管理Clocks
4.1简介
第3章我们描述了clock子系统的功能之一 :向底层的clock driver提供注册接口.
本章我们描述clock子系统的功能之二 :如何管理这些clocks.
当clock driver向子系统注册某一个clock的时候,子系统内部会创建一个数据结构来表示这个clock. 这个数据结构在前文提过,就是struct clk.
一个struct clk就对应一个clock.到目前为止,我们还没见过这个数据结构的庐山真面目呢!本章会围绕这个数据结构展开,充分理解它,你就里面本章了.
4.2主要数据结构
structclk
结构体定义在一个C文件里面 : drivers/clk/clk.c
struct clk
Comment
struct clk_core*core
clk_core是clock核心层的一个私有数据结构,下面细述
const char *dev_id
使用该clk的device的name
const char *con_id
connection ID string on device
unsigned long min_rate
最小rate
unsigned long max_rate
最大rate
struct hlist_node clks_node
这个结构体有点颠覆本文的一个观点,那就是:一个struct clk结构体对应一个具体的clock.
仔细阅读源码后发现,原来每当某一个driver尝试获取某个clock的时候, clock子系统就会创建一个struct clk.
例如假设有个clock,名称是pll_clk. GPIO, SPI, I2C这3个模块都是用此pll_clk做为工作时钟的.
那么clock子系统核心层会有一个clk_core用于描述pll_clk,每当GPIO/SPI/I2C的driver首次尝试获取pll_clk时, clock子系统就会创建一个struct clk结构体,并将创建的这个struct clk返回给对应的driver.该clk结构体的dev_id和con_id都是对应的driver指定的.
不过为了简便起见,我们在本文中还是暂定一个struct clk对应一个clock吧,理解起来不会有什么异常.
structclk_core
前文我们说过,一个struct clk就代表一个clock.一个clk_core与clock也是一一对应的关系.那它俩有什么区别呢?
struct clk更像是对外的接口,例如当provider向clock子系统注册时,它会得到一个struct clk*的返回结果;当consumer想要使用某个clock时,也会首先获取struct clk*这个结构体.
struct clk_core则是clock子系统核心层的一个私有数据结构,在核心层代描述某个具体的clock. provider或consumer不会触碰这个数据结构.
结构体定义在一个C文件里面 : drivers/clk/clk.c
structclk_core
Comment
const char*name
此clock的name, provider在注册的时候会提供clock的name
const struct clk_ops*ops
操作此clock的ops, provider在注册的时候会提供clock的ops
struct clk_hw*hw
provider端提供的clk_hw结构体
struct module*owner
struct clk_core*parent
一个clock可能有多个parent,但是同一时刻只能有一个parent有效.
这里指向有效的那个父时钟所对应的clk_core
const char**parent_names
所有可选的父时钟的names
struct clk_core**parents
所有可选的父时钟的clk_core
u8num_parents
有多少个parents
u8new_parent_index
你可以把所有的parents理解成一个数组, index就对应数组的某个元素
unsigned longrate
clock的rate
unsigned longreq_rate
consumer端要求的rate
unsigned longnew_rate
新的rate
struct clk_core*new_parent
新的parent
struct clk_core*new_child
新的child
unsigned longflags
此clock的flags,可选的flag在3.3节《struct clk_init_data》中有介绍
unsigned intenable_count
被使能的次数
unsigned intprepare_count
被prepare的次数
unsigned longaccuracy
此clock当前的accuracy, The clock accuracy is expressed in ppb (parts per billion)
intphase
此clock当前的phase,取值范围 0 – 359
struct hlist_headchildren
链表头,用于挂接本clock所有的children
struct hlist_nodechild_node
链表节点,用于把本clock挂接到对应的parent的children链表下
struct hlist_nodedebug_node
链表节点,与debugfs相关
struct hlist_headclks
一个struct clk_core可能对应多个struct clk,这个链表头挂接所有的clks.
关于struct clk_core和struct clk的关系,参见4.2节《struct clk》的说明
unsigned intnotifier_count
内核通知链机制相关.
当内核其它模块想知道某个clock的变化时 (例如频率等的改变),可以向此clock的通知链注册.这样当clock发生变化时,就会向通知链上的所有模块发送通知
notifier_count代表有多少个模块向本clock注册了
struct dentry*dentry
debugfs相关
struct krefref
引用计数
4.3 关键代码分析
clk_register
我们在3.4节介绍了clock子系统提供给provider端的总多clk_register_xxx函数.这些函数都是对clk_register的封装,基础都是clk_register.
因此我们本章只重点讲解这一个API,其它API有兴趣可以自己阅读源码.
在开始分析之前,结合之前讲解的内容,你能猜到clk_register会做哪些事情吗?
首先,它得创建struct clk和struct clk_core这两个数据结构.
然后,它得维护clk_core的树形关系,就像时钟数的硬件形态那样:
有一个或多个ROOT_CLOCK, ROOT_CLOCK没有parent,下面挂载的是children, children下面在挂载children.
为什么要维护这样的树形结构呢?因为我们在操作某个clk时,往往会跟它的parent有关:例如要使能某个clk,必须保证它的parent也使能;或者parent的rate改变了,那它的children的rate都有可能改变.因此clock子系统必须维护好树形结构,才能方便的处理这些相关性.
下面我们来看看代码:
实现文件: drivers/clk/clk.c
/**
* clk_register – allocate a new clock, register it and return an opaque cookie
* @dev: device that is registering this clock
* @hw: link to hardware-specific clock data
*
*clk_registeris the primary interface for populating the clock tree with new
*clocknodes. It returns a pointer to the newly allocated struct clk which
* cannot be dereferenced by driver code but may be used in conjuction with the
*restof the clock API. In the event of an error clk_register will return an
*errorcode; drivers must test for an error code after calling clk_register.
*/
structclk*clk_register(structdevice*dev, structclk_hw*hw)
{
inti,ret;
structclk_core*clk;
clk =kzalloc(sizeof(*clk),GFP_KERNEL);
if (!clk) {
pr_err(“%s: could not allocate clk\n”,__func__);
ret = –ENOMEM;
gotofail_out;
}
clk->name=kstrdup_const(hw->init->name,GFP_KERNEL);
if (!clk->name) {
pr_err(“%s: could not allocate clk->name\n”,__func__);
ret = –ENOMEM;
gotofail_name;
}
clk->ops=hw->init->ops;
if (dev&&dev->driver)
clk->owner=dev->driver->owner;
clk->hw=hw;
clk->flags=hw->init->flags;
clk->num_parents=hw->init->num_parents;
hw->core=clk;
/* allocate local copy in case parent_names is __initdata */
clk->parent_names=kcalloc(clk->num_parents, sizeof(char *),
GFP_KERNEL);
if (!clk->parent_names) {
pr_err(“%s: could not allocate clk->parent_names\n”,__func__);
ret = –ENOMEM;
gotofail_parent_names;
}
/* copy each string name in case parent_names is __initdata */
for (i= 0;inum_parents;i++) {
clk->parent_names[i] =kstrdup_const(hw->init->parent_names[i],
GFP_KERNEL);
if (!clk->parent_names[i]) {
pr_err(“%s: could not copy parent_names\n”,__func__);
ret = –ENOMEM;
gotofail_parent_names_copy;
}
}
INIT_HLIST_HEAD(&clk->clks);
hw->clk= __clk_create_clk(hw, NULL, NULL);
if (IS_ERR(hw->clk)) {
pr_err(“%s: could not allocate per-user clk\n”,__func__);
ret =PTR_ERR(hw->clk);
gotofail_parent_names_copy;
}
ret = __clk_init(dev,hw->clk);
if (!ret)
returnhw->clk;
__clk_free_clk(hw->clk);
hw->clk= NULL;
fail_parent_names_copy:
while (–i>= 0)
kfree_const(clk->parent_names[i]);
kfree(clk->parent_names);
fail_parent_names:
kfree_const(clk->name);
fail_name:
kfree(clk);
fail_out:
returnERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(clk_register);
上面这段代码的逻辑比较简单:
首先,创建了clk_core结构体,然后用provider提供的clk_hw,填充clk_core中的各个字段.
然后,调用 __clk_create_clk,在__clk_create_clk里面会创建struct clk这个结构体并初始化其相关字段. clk_register会返回给provider一个struct clk*的结构体指针,这个clk*就是这里创建的.
下文我们会单独介绍__clk_create_clk这个函数.
最后,会调用__clk_init,在__clk_init里面会处理clk_core之间的树形结构关系.
下文我们会单独介绍__clk_init这个函数.
__clk_create_clk
这个API会创建一个struct clk结构体.
要理解此API的细节,首先得理清楚struct clk和struct clk_core的关系.
我们在4.2节《struct clk_core》中已经初步介绍了clk和clk_core的关系,可以回头看看.
这里我们在做进一步的说明.
还记得我们在《字符设备驱动》一文中介绍过的struct file和struct inode这两个结构体吗? inode在物理上代表一个文件,一个文件对应唯一一个inode; file则代表一个打开的文件,文件被打开几次,就会有几个file.
clk和clk_core的关系与上面很类型, clk_core代表一个硬件上的clock,一个clock对应唯一一个clk_core; 而clk则代表被使用的clock,例如GPIO driver想要获取某个clock,它会得到一个struct clk, SPI driver想要获取同一个clock,它也会得到一个struct clk,另外,当此clock的provider向clock子系统注册时, provider也会得到一个struct clk.
接下来我们看看代码细节吧:
structclk*__clk_create_clk(structclk_hw*hw, const char *dev_id,
const char *con_id)
{
structclk*clk;
/*Thisis to allow this function to be chained to others */
if (!hw||IS_ERR(hw))
return (structclk*)hw;
clk =kzalloc(sizeof(*clk),GFP_KERNEL);
if (!clk)
returnERR_PTR(-ENOMEM);
clk->core=hw->core;
clk->dev_id=dev_id;
clk->con_id=con_id;
clk->max_rate=ULONG_MAX;
clk_prepare_lock();
hlist_add_head(&clk->clks_node, &hw->core->clks);
clk_prepare_unlock();
returnclk;
}
逻辑比较简单:
首先创建struct clk结构体,然后初始化结构体的相关参数,最后把这个struct clk挂载到clk_core的clks链表头下面.
__clk_init
这个API主要是处理clock之间的树形关系.
当任何一个clock调用clk_register接口进行注册时, __clk_init都会负责把这个clock放在树形结构的恰当位置.
代码细节如下:
1: /**
2: * __clk_init – initialize the data structures in a struct clk
3: * @dev: device initializing this clk, placeholder for now
4: * @clk: clk being initialized
5: *
6: * Initializes the lists in struct clk, queries the hardware for the
7: * parent and rate and sets them both.
8: */
9: int__clk_init(structdevice*dev, structclk*clk)
10: {
11:inti,ret= 0;
12:structclk*orphan;
13:structhlist_node*tmp2;
14:
15:if (!clk)
16:return –EINVAL;
17:
18:clk_prepare_lock();
19:
20:/* check to see if a clock with this name is already registered */
21:if (__clk_lookup(clk->name)) {
22:pr_debug(“%s: clk %s already initialized\n”,
23:__func__,clk->name);
24:ret= –EEXIST;
25:gotoout;
26:}
27:
28:/* check that clk_ops are sane. See Documentation/clk.txt */
29:if (clk->ops->set_rate&&
30:!(clk->ops->round_rate&&clk->ops->recalc_rate)) {
31:pr_warning(“%s: %s must implement .round_rate & .recalc_rate\n”,
32:__func__,clk->name);
33:ret= –EINVAL;
34:gotoout;
35:}
36:
37:if (clk->ops->set_parent&& !clk->ops->get_parent) {
38:pr_warning(“%s: %s must implement .get_parent & .set_parent\n”,
39:__func__,clk->name);
40:ret= –EINVAL;
41:gotoout;
42:}
43:
44:/* throw a WARN if any entries in parent_names are NULL */
45:for (i= 0;inum_parents;i++)
46:WARN(!clk->parent_names[i],
47:“%s: invalid NULL in %s’s .parent_names\n”,
48:__func__,clk->name);
49:
50:/*
51: * Allocate an array of struct clk *’s to avoid unnecessary string
52: * look-ups of clk’s possible parents. This can fail for clocks passed
53: * in to clk_init during early boot; thus any access to clk->parents[]
54: * must always check for a NULL pointer and try to populate it if
55: * necessary.
56: *
57: * If clk->parents is not NULL we skip this entire block. This allows
58: * for clock drivers to statically initialize clk->parents.
59: */
60:if (clk->num_parents> 1 && !clk->parents) {
61:clk->parents= kzalloc((sizeof(structclk*) *clk->num_parents),
62:GFP_KERNEL);
63:/*
64: * __clk_lookup returns NULL for parents that have not been
65: * clk_init’d; thus any access to clk->parents[] must check
66: * for a NULL pointer. We can always perform lazy lookups for
67: * missing parents later on.
68: */
69:if (clk->parents)
70:for (i= 0;inum_parents;i++)
71:clk->parents[i] =
72:__clk_lookup(clk->parent_names[i]);
73:}
74:
75:clk->parent=__clk_init_parent(clk);
76:
77:/*
78: * Populate clk->parent if parent has already been __clk_init’d. If
79: * parent has not yet been __clk_init’d then place clk in the orphan
80: * list. If clk has set the CLK_IS_ROOT flag then place it in the root
81: * clk list.
82: *
83: * Every time a new clk is clk_init’d then we walk the list of orphan
84: * clocks and re-parent any that are children of the clock currently
85: * being clk_init’d.
86: */
87:if (clk->parent)
88:hlist_add_head(&clk->child_node,
89:&clk->parent->children);
90:else if (clk->flags&CLK_IS_ROOT)
91:hlist_add_head(&clk->child_node, &clk_root_list);
92:else
93:hlist_add_head(&clk->child_node, &clk_orphan_list);
94:
95:/*
96: * Set clk’s rate. The preferred method is to use .recalc_rate. For
97: * simple clocks and lazy developers the default fallback is to use the
98: * parent’s rate. If a clock doesn’t have a parent (or is orphaned)
99: * then rate is set to zero.
100: */
101:if (clk->ops->recalc_rate)
102:clk->rate=clk->ops->recalc_rate(clk->hw,
103:__clk_get_rate(clk->parent));
104:else if (clk->parent)
105:clk->rate=clk->parent->rate;
106:else
107:clk->rate= 0;
108:
109:/*
110: * walk the list of orphan clocks and reparent any that are children of
111: * this clock
112: */
113:hlist_for_each_entry_safe(orphan,tmp2, &clk_orphan_list,child_node) {
114:if (orphan->ops->get_parent) {
115:i=orphan->ops->get_parent(orphan->hw);
116:if (!strcmp(clk->name,orphan->parent_names[i]))
117:__clk_reparent(orphan,clk);
118:continue;
119:}
120:
121:for (i= 0;inum_parents;i++)
122:if (!strcmp(clk->name,orphan->parent_names[i])) {
123:__clk_reparent(orphan,clk);
124:break;
125:}
126:}
127:
128:/*
129: * optional platform-specific magic
130: *
131: * The .init callback is not used by any of the basic clock types, but
132: * exists for weird hardware that must perform initialization magic.
133: * Please consider other ways of solving initialization problems before
134: * using this callback, asit’suse is discouraged.
135: */
136:if (clk->ops->init)
137:clk->ops->init(clk->hw);
138:
139:clk_debug_register(clk);
140:
141:out:
142:clk_prepare_unlock();
143:
144:returnret;
145: }
这一段代码的逻辑很复杂,主要做的事情如下:
20~26行,以clock name为参数,调用__clk_lookup接口,查找是否已有相同name的clock注册,如果有,则返回错误。由此可以看出,clock framework以name唯一识别一个clock,因此不能有同名的clock存在
28~42行,检查clk ops的完整性,例如:如果提供了set_rate接口,就必须提供round_rate和recalc_rate接口;如果提供了set_parent,就必须提供get_parent
50~73行,分配一个struct clk *类型的数组,缓存该clock的parents clock。具体方法是根据parents_name,查找相应的struct clk指针
75行,获取当前的parent clock,并将其保存在parent指针中。具体可参考下面“说明2”
77~93行,根据该clock的特性,将它添加到clk_root_list、clk_orphan_list或者parent->children三个链表中的一个,具体请参考下面“说明1”
95~107行,计算clock的初始rate,具体请参考下面“说明3”
109~126行,尝试reparent当前所有的孤儿(orphan)clock,具体请参考下面“说明4”
128~137行,如果clock ops提供了init接口,执行之(由注释可知,kernel不建议提供init接口)
说明1:clock的管理和查询
clock framework有2条全局的链表:clk_root_list和clk_orphan_list。所有设置了CLK_IS_ROOT属性的clock都会挂在clk_root_list中。其它clock,如果有valid的parent,则会挂到parent的“children”链表中,如果没有valid的parent,则会挂到clk_orphan_list中。
查询时(__clk_lookup接口做的事情),依次搜索:
clk_root_list–>root_clk–>children–>child’s children
clk_orphan_list–>orphan_clk–>children–>child’s children
即可
说明2:当前parent clock的选择(__clk_init_parent)
对于没有parent,或者只有1个parent的clock来说,比较简单,设置为NULL,或者根据parent name获得parent的struct clk指针接。
对于有多个parent的clock,就必须提供.get_parent ops,该ops要根据当前硬件的配置情况,例如寄存器值,返回当前所有使用的parent的index(即第几个parent)。然后根据index,取出对应parent clock的struct clk指针,作为当前的parent。
说明3:clock的初始rate计算
对于提供.recalc_rate ops的clock来说,优先使用该ops获取初始的rate。如果没有提供,退而求其次,直接使用parent clock的rate。最后,如果该clock没有parent,则初始的rate只能选择为0
.recalc_rate ops的功能,是以parent clock的rate为输入参数,根据当前硬件的配置情况,如寄存器值,计算获得自身的rate值
说明4:orphan clocks的reparent
有些情况下,child clock会先于parent clock注册,此时该child就会成为orphan clock,被收养在clk_orphan_list中。
而每当新的clock注册时,kernel都会检查这个clock是否是某个orphan的parent,如果是,就把这个orphan从clk_orphan_list中移除,放到新注册的clock的怀抱。这就是reparent的功能,它的处理逻辑是:
遍历orphan list,如果orphan提供了.get_parent ops,则通过该ops得到当前parent的index,并从parent_names中取出该parent的name,然后和新注册的clock name比较,如果相同,呵呵,找到parent了,执行__clk_reparent,进行后续的操作
如果没有提供.get_parent ops,只能遍历自己的parent_names,检查是否有和新注册clock匹配的,如果有,执行__clk_reparent,进行后续的操作
__clk_reparent会把这个orphan从clk_orphan_list中移除,并挂到新注册的clock上。然后调用__clk_recalc_rates,重新计算自己以及自己所有children的rate。计算过程和上面的clock rate设置类似
4.clockconsumer —如何使用Clocks
5.1简介
clock子系统管理clocks的最终目的,是让device driver可以方便的获取并使用这些clocks.
我们知道clock子系统用一个struct clk结构体来抽象某一个clock.
当device driver要操作某个clock时,它需要做两件事情:
首先,获取clock.也叫clk_get.
然后,操作这个clock.如 clk_prepare/clk_enable/clk_disable/clk_set_rate/…
举个例子,以GPIO控制器为例.从硬件的角度来说, GPIO控制器需要工作时钟.因此,在GPIO控制器的platform_driver的probe函数里面,就需要操作这个时钟.
操作的第一步是获取clock,获取clock有两种方式:
第一种方式是直接通过name来获取,例如假设已知GPIO控制器的工作时钟的name是”gpio_clk”,那么我们就可以在probe函数里面通过clk_get(&device, “gpio_clk”) 这种方式获取.
另外一种方式是说,对于GPIO控制器来讲,我们会有一个platform_device,在里面描述GPIO控制器的资源,也就是寄存器地址,中断号什么的. GPIO控制器的工作时钟也算一种资源了,我们能否在platform_device里面一并描述这个资源呢?
答案是可以. platform_device现在都是在DTS中描述的,因此这个GPIO控制器的DTS可以这样写:
gpio :gpio-controller@xxxx {
compatible=“yyyy”;
reg= ;
……
clocks = ; /*指明/引用某一个clock*/
}
我们用clocks这个property来描述时钟资源.
在对应的platform_driver的probe函数里面,我们就可以通过clk_get(&device,NULL)这种方式来获取所需的clock.
这种方式下,我们只需要知道platform_device,然后就能通过它找到对应的DTS node,然后就能通过DTS node的”clocks”property获取到对应的时钟资源.
获取到clk之后,就可以通过clock子系统提供的API来操作clk了.
clock子系统提供给consumer端的APIs都定义在头文件 include/linux/clk.h里面.
接下来的章节,我们会分两部分来介绍这些APIs.
5.2节介绍获取clk相关的APIs.
5.3节介绍操作clk相关的APIs.
5.2APIs– 获取clk
头文件: include/linux/clk.h
实现文件: drivers/clk/clkdev.c
最主要的两个API:
structclk*clk_get(structdevice*dev, const char *id);
structclk*devm_clk_get(structdevice*dev, const char *id);
devm_clk_get是devm版本的clk_get.用于自动释放资源,我们在《设备模型》一文中有过介绍,这里不多说了.
除了这两个用的最多的API之外,还有一些其他的API,如下:
structclk*clk_get_sys(const char *dev_id, const char *con_id)
structclk*of_clk_get(structdevice_node*np, intindex);
structclk*of_clk_get_by_name(structdevice_node*np, const char *name);
structclk*of_clk_get_from_provider(structof_phandle_args*clkspec);
clk_get相当于一个总逻辑,它会根据不同的情况调用上述这些API,在实际代码中,基本上我们只会用到clk_get或者devm_clk_get.
接下来,我们会仔细看看clk_get的内部逻辑.
clk_get / devm_clk_get
头文件: include/linux/clk.h
实现文件: drivers/clk/clkdev.c
原型:structclk*clk_get(structdevice*dev, const char *id);
该API的主要作用就是根据参数,从clock子系统中获取一个clk给到consumer,然后consumer就可以操作该clk了.因此该API的返回值就是用于描述某个clock的数据结构:structclk.
代码细节如下:
structclk*clk_get(structdevice*dev, const char *con_id)
{
const char *dev_id=dev?dev_name(dev) : NULL;
structclk*clk;
if (dev) {
clk =__of_clk_get_by_name(dev->of_node,dev_id,con_id);
if (!IS_ERR(clk) ||PTR_ERR(clk) == –EPROBE_DEFER)
returnclk;
}
returnclk_get_sys(dev_id,con_id);
}
EXPORT_SYMBOL(clk_get);
如果你已经充分理解了2.3节的那个系统框图,那么此处的逻辑就很简单了:
如果dev不为空,那么就以dev->of_node为参数,调用of_XXX那一套,从LIST_HEAD(of_clk_providers)这个池子里面查询某个clk.
如果查询到了,则返回该clk.这种情况其实对应5.1节中描述的DTS node方式.
如果上面没有获取到clk,则调用clk_get_sys,从LIST_HEAD(clocks)这个池子里面查询clk.查询的关键字是con_id,其实就是clock的name.
5.3APIs– 操作clk
头文件: include/linux/clk.h
实现文件: drivers/clk/clk.c
1: intclk_prepare(structclk*clk)
2: voidclk_unprepare(structclk*clk)
3:
4: staticinlineintclk_enable(structclk*clk)
5: staticinlinevoidclk_disable(structclk*clk)
6:
7: staticinlineunsigned longclk_get_rate(structclk*clk)
8: staticinlineintclk_set_rate(structclk*clk, unsigned longrate)
9: staticinlinelongclk_round_rate(structclk*clk, unsigned longrate)
10:
11: staticinlineintclk_set_parent(structclk*clk, structclk*parent)
12: staticinlinestructclk*clk_get_parent(structclk*clk)
13:
14: staticinlineintclk_prepare_enable(structclk*clk)
15: staticinlinevoidclk_disable_unprepare(structclk*clk)
clk_enable/clk_disable:启动/停止clock.不会睡眠
clk_prepare/clk_unprepare:启动clock前的准备工作/停止clock后的善后工作.可能会睡眠
clk_get_rate/clk_set_rate/clk_round_rate:clock频率的获取和设置,其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误.如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值
clk_set_parent/clk_get_parent:获取/选择clock的parent clock
clk_prepare_enable:将clk_prepare和clk_enable组合起来,一起调用
clk_disable_unprepare:将clk_disable和clk_unprepare组合起来,一起调用
prepare/unprepare,enable/disable的说明:
这两套API的本质,是把clock的启动/停止分为atomic和non-atomic两个阶段,以方便实现和调用。
因此上面所说的“不会睡眠/可能会睡眠”,有两个角度的含义:
一是告诉底层的clock driver,请把可能引起睡眠的操作,放到prepare/unprepare中实现,一定不能放到enable/disable中
二是提醒上层使用clock的driver,调用prepare/unprepare接口时可能会睡眠哦,千万不能在atomic上下文(例如中断处理中)调用哦,而调用enable/disable接口则可放心。
另外,clock的开关为什么需要睡眠呢?这里举个例子,例如enable PLL clk,在启动PLL后,需要等待它稳定。而PLL的稳定时间是很长的,这段时间要把CPU交出(进程睡眠),不然就会浪费CPU。
最后,为什么会有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果调用者能确保是在non-atomic上下文中调用,就可以顺序调用prepare/enable、disable/unprepared,为了简单,framework就帮忙封装了这两个接口。
5.4其它APIs
头文件: include/linux/clk.h
实现文件: drivers/clk/clk.c
1: intclk_notifier_register(structclk*clk, structnotifier_block*nb);
2: intclk_notifier_unregister(structclk*clk, structnotifier_block*nb);
这两个API与内核提供的通知链机制有关.
这两个notify接口,用于注册/注销 clock rate改变的通知.
例如某个driver关心某个clock,期望这个clock的rate改变时,通知到自己,就可以注册一个notify.
本文标签: linux 庐山培训 Linux
版权声明:本文标题:linux 庐山培训,Linux 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1731217369h1470407.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论