kernel 称 clock driver 为 clock provider(相应的 clock 的使用者为clock consumer)。clock device是指那些能够产生clock信号、控制clock信号的设备,如:
总线下面有device,如果device的频率高了,总线抗不住了就需要升频,如 APB 总线下面常常挂在一些外设,一些外设的时钟是由APB时钟分配出来的,外设需要提升数据传输速率,APB总线就需要提升clk, 也就是说SoC数据中的数据是通过APB总线与外设进行交互的。因为数据的收发是需要cpu先将数据放到移位寄存器中去,然后通过总线按bit发送出去,SoC中的一些controller的读写是通过什么总线进行操作的呢??
这个需要看设备接到哪了,比如i2c uart等接在了apb上,cpu准备好数据,发到axi,再到axi-to-apb,再到具体controller。
PCLK APB总线时钟。
PRESETn APB总线复位。低有效。
PADDR 地址总线。
PSELx 从设备选择。
PENABLE APB传输选通。
PWRITE 高为写传输,低为读。
PRDATA 读数据总线。
PWDATA 写数据总线。
总线的简单通信过程是这样的:
系统中的 clock distribution 形成了类似文件系统那样的树状结构,和文件系统树不同的是: clock tree有多个根节点,形成多个clock tree,而文件系统树只有一个根节点;文件系统树的中间节点是目录,叶节点是文件,而clock tree相对会复杂一些,它包括如下的的节点:
根节点: 一般是Oscillator(有源振荡器) 或者Crystal (无源振荡器,即经常说的晶振)。
中间节点:有很多种,包括 PLL (锁相环,用于提升频率的),Divider(分频器,用于降频的),mux(从多个clock path中选择一个),开关(用来控制ON/OFF的)。
叶节点: 是使用 clock 做为输入的、有具体功能的 HW block。对于叶节点,或者说clock consumer而言,没有什么可以控制的,HW block只是享用这个clock source而已。
虽然 HW clock 的 datasheet 往往有clock gating 的内容(或者一些针对clock source进行分频的内容,概念类似), 但是,本质上负责 clock gating 的那个硬件模块(即对于clock的控制寄存器)需要独立出来,称为clock tree中的一个中间节点。而对中间节点的设定包括:
这个模块是真正和系统中实际的 clock device打交道的模块。与其说 Clock provider driver,不如说是clock provider drivers(复数),主要用来驱动系统中的 clock tree 中的各个节点上 clock device(不包括clock consumer device), 只要该HW block对外提供时钟信号,那么它就是一个clock provider,就有对应的 clock provider driver。
系统中的 clock device 种类很多,如:VCO、分频器,复用器;其功能各不相同,但是本质上都属于clock device,Linux kernel(CCF)从clock provider的角度描述clock,把这些 clock HW block 的特性抽取出来,用struct clk_hw 来表示,具体如下:
系统中提供和时钟有关的硬件很多,如上文描述,既然是硬件描述,所以使用clk_hw(clock hardware), 可以看出Linux对命名的准确追求。
struct clk_hw {
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
};
struct clk_core *core
: 指向CCF模块中对应clock device实例。由于系统中的clk_hw 和 clk_core 实例是一 一对应的,因此,struct clk_core中也有指回 clk_hw 的数据成员。struct clk *clk
: clk 是访问 clk_core 的实例,每当consumer通过 clk_get 对CCF中的 clock device (也就是clk_core)发起访问的时候都需要获取一个句柄,也就是 clk。每一个用户访问都会有一个clk句柄, 然后找到 clk_core,之后即可通过clk_core 的 struct clk_ops中的 callback 函数可以进入具体的clock provider driver层进行具体的 HW 操作。struct clk指针,由clock framework分配并维护,并在需要时提供给clock consumer使用,struct clk_ops 这个数据结构是clock consumer 通过 clk_get 获得相关 clk 的句柄后并使用该 clk 句柄对 clock 的相关寄存器进行设置时调用的函数集,该函数集在进行相关 clock 注册时通过 clk_init_data 结构带入核心层CCF的。const struct clk_init_data *init
: 在底层clock provider driver初始化的过程中,会调用 clk_register 接口函数注册clk_hw。当然,这时候需要设定一些初始数据,而这些初始数据被抽象成一个struct clk_init_data数据结构。在初始化过程中,clk_init_data的数据被用来初始化 clk_hw 对应的 clk_core数据结构,当初始化完成之后,clk_init_data则没有存在的意义了。struct clk_init_data {
const char *name;
const struct clk_ops *ops;
const char **parent_names;
u8 num_parents;
unsigned long flags;
};
struct clk_ops {
int (*prepare)(struct clk_hw *hw);
void (*unprepare)(struct clk_hw *hw);
int (*is_prepared)(struct clk_hw *hw);
void (*unprepare_unused)(struct clk_hw *hw);
int (*enable)(struct clk_hw *hw);
void (*disable)(struct clk_hw *hw);
int (*is_enabled)(struct clk_hw *hw);
void (*disable_unused)(struct clk_hw *hw);
unsigned long (*recalc_rate)(struct clk_hw *hw,
prepare/unprepare,enable/disable
的说明:
这两套API的本质,是把clock的启动/停止分为atomic和non-atomic两个阶段,以方便实现和调用。因此上面所说的“不会睡眠/可能会睡眠”,有两个角度的含义:
1)首先,在DTS(device tree source)中,指定device需要使用的clock,如下:
device {
clocks = <&osc 1>, <&ref 0>;
clock-names = "baud", "register";
};
该DTS的含义是:
device 需要使用两个 clock,“baud” 和 “regitser”,由 clock-names
关键字指定;
Q: 那么问题来了,clocks关键字中,<&osc 1>样式的字段是怎么来的?
A: 是由clock的provider,也就是底层clock driver规定的(具体会在下一篇文章讲述)。所以使用clock时,一定要找clock driver拿相关的信息(一般会放在“Documentation/devicetree/bindings/clock
/”目录下);
2)系统启动后,device tree会解析clock有关的关键字,并将解析后的信息放在platform_device相关的字段中;
3)具体的driver可以在probe时,以clock的名称(不提供也行)为参数,调用clk get接口,获取clock的句柄,然后利用该句柄,可直接进行enable、set rate等操作。
这个数据结构是CCF层对 clock device(真正的 clock 相关寄存器)的抽象,每一个实际的硬件 clock device(struct clk_hw)都会对应一个clk_core,CCF模块负责建立整个抽象的 clock tree 的树状结构并维护这些数据。具体如何维护, 这里给出几个链表头的定义,如下:
static HLIST_HEAD(clk_root_list);
static HLIST_HEAD(clk_orphan_list);
CCF layer 有 2 条全局的链表:
所有设置了CLK_IS_ROOT
属性的 clock 都会挂在 clk_root_list
中,而这个链表中的每一个阶段又展成一个树状结构。(这和硬件拓扑是吻合的,clock tree 实际上是有多个根节点的,多条树状结构)。
其它clock,如果有valid 的 parent,则会挂到parent的“children”链表中,如果没有valid 的 parent,则会挂到 clk_orphan_list
中。
基本上,每一个user对clock device的访问都会创建一个访问句柄,这个句柄就是clk。不同的user访问同样的clock device的时候,虽然是同一个struct clk_core
实例,但是其访问的句柄 (clk) 是不一样的,即所获得的clk句柄地址可以不一样,但是clk句柄都是针对某个clock硬件。
/* include/linux/clk-private.h */
struct clk {
const char *name;
const struct clk_ops *ops;
struct clk_hw *hw;
struct clk *parent;
const char **parent_names;
struct clk **parents;
u8 num_parents;
unsigned long rate;
unsigned long new_rate;
unsigned long flags;
unsigned int enable_count;
unsigned int prepare_count;
struct hlist_head children;
struct hlist_node child_node;
unsigned int notifier_count;
#ifdef CONFIG_COMMON_CLK_DEBUG
struct dentry *dentry;
#endif
};
成员解释:
name, ops, hw, parents_name, num_parents, flags: 可参考1.2 节中的相关描述;
parents:一个指针数组,保存了所有可能的 parent clock 的 struct clk 指针;因为如果你个clk需要变频的时候可能当前的parent满足不了,需要切换到其他parent上。
rate:当前的clock rate;
new_rate:新设置的clock rate,之所要保存在这里,是因为set rate过程中有一些中间计算,后面再详解;
enable_count, prepare_count:该clock 被 enable 和 prepare 的次数,用于确保enable/disable以及prepare/unprepare的成对调用;
children:该clock的 children clocks(孩儿们),以链表的形式组织;
child_node:一个list node,自己作为child时,挂到parent的children list时使用;
notifier_count:记录注册到notifier的个数。
基本上每一个clock provider都会变成 dts 中的一个节点,也就是说,每一个clk都有一个设备树中的device node与之对应。在这种情况下,捆绑 clk 和 device node,具体的数据结构如下:
struct of_clk_provider {
struct list_head link; ----- 挂入of_clk_providers全局链表
struct device_node *node;----该clock device的DTS节点
// 获取对应clk 数据结构的函数
struct clk *(*get)(struct of_phandle_args *clkspec, void *data);
void *data;
};
因此,对于底层 provider driver而言,clk_register
+ of_clk_add_provider
的组合,在CCF layer保存了of_clk_providers全局链表来管理所有的 DTS 节点和 clk的对应关系。
在我们的项目中并不是这样处理的,而是使用了一个设备节点。
可以通过 clock consumer 对应的 struct device_node, 寻找为它提供clock signal那个clock设备对应的 device node(clock属性和clock-names属性),当然,如果consumer有多个clock signal来源,那么在寻找的时候需要告知是要找哪一个时钟源(用connection ID标记)。
当找了provider对应的device node之后,一切都变得简单了,从全局的clock provide链表中找到对应clk就OK了。
系统中,每一个clock 都有一个 struct clk_hw 变量描述,clock provider 需要使用 register相关的接口,将这些clock注册到kernel,clock framework的核心代码会把它们转换为struct clk变量,并以tree的形式组织起来。这些接口的原型如下:
struct clk *clk_register(struct device *dev, struct clk_hw *hw);
struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw)
void clk_unregister(struct clk *clk);
void devm_clk_unregister(struct device *dev, struct clk *clk);
register 接口接受一个填充好的 struct clk_hw
指针,将它转换为sruct clk
结构,并根据 parent 的名字,添加到 clock tree中。不过,clock framework 所做的远比这周到,它基于clk_register
,又封装了其它接口, 使clock provider 在注册clock时,连struct clk_hw都不需要关心。
首先,在DTS(device tree source)中,指定 device 需要使用的 clock,如,例-1.4 所示:
device {
clocks = <&osc 1>, <&ref 0>;
clock-names = "baud", "register";
};
该DTS的含义是:
device 需要使用两个 clock,“baud” 和 “regitser”,由clock-names关键字指定;
baud 取自 “osc” 的输出 1,register 取自“ref” 的输出 0,由clocks关键字指定。
clocks关键字中,<&osc 1>样式的字段是由 clock 的 provider,也就是底层clock driver规定的。指明该设备的clock列表,clk_get 时,会以它为关键字,去device_node中搜索,以得到对应的struct clk指针;
clocks 需要指明的信息,由clock provider的 “#clock-cells
” 规定:
为 0 时,只需要提供一个clock provider name(称作phandle);
为 1 时,表示phandle有多个输出,则需要额外提供一个ID,指明具体需要使用那个输出。
以 qucomC 为例:arch/arm64/boot/dts/tiger/qucom-common.dtsi
, 如下,例-1.5 所示。
soc_clocks: clocks@d4050000{
compatible = "tiger,qucom-clock";
reg = <0x0 0xd4050000 0x0 0x209c>, //根据寄存器的名字找到地址
<0x0 0xd4282800 0x0 0x400>, //名字reg-names:见下面内容
<0x0 0xd4015000 0x0 0x1000>,
<0x0 0xd4090000 0x0 0x1000>,
<0x0 0xd4282c00 0x0 0x400>,
<0x0 0xd8440000 0x0 0x98>,
<0x0 0xd4200000 0x0 0x4280>;
reg-names = "mpmu", "apmu", "apbc", "apbs", "ciu",
"dciu", "ddrc";
interrupts = <0 IRQ_QUCOM_AP_PMU IRQ_TYPE_LEVEL_HIGH>;
#clock-cells = <1>;
};
上面的 例-1.4 是直接用立即数表示,更好的做法是,将系统所有clock 的 ID,定义在一个头文件中,而 DTS可以包含这个头文件,qucomC 中 clock 的 ID 头文件文件位于:include/dt-bindings/clock/tiger-qucom.h如:
#define QUCOM_CLK_AUDIO_FNC 570
#define QUCOM_CLK_AUDIOIPC 671
#define QUCOM_CLK_PLL7_D4 303
adsp@d401d100 {
compatible = "tiger,tiger_adsp";
reg = <0 0xd401d100 0 0x300>,
<0 0xc088c000 0 0x2000>;
reg-names = "ipcreg", "bootc";
clocks = <&soc_clocks QUCOM_CLK_AUDIO_FNC>,
<&soc_clocks QUCOM_CLK_AUDIOIPC>,
<&soc_clocks QUCOM_CLK_PLLA_D5>;
clock-names = "adspcore", "adspipc", "adsp_pll7_d4";
/*power-domains = <&qucom_pds QUCOM_PD_COMMON_AUDIO>;
interrupt-names = "asp_ipc_irq";
interrupts = <0 IRQ_QUCOM_AP2ADSP_IPC 0x4>;
status = "ok";
};
clock-names,为clocks 指定的那些clock分配一些易于使用的名字,driver可以直接以名字为参数,get clock 的句柄。
系统启动后,device tree 会解析 clock 有关的关键字,并将解析后的信息放在 platform_device 相关的字段中。
根据 clock 的特点,clock framework将clock分为 6类:
每一类 clock 都有相似的功能、相似的控制方式,因而可以使用相同的逻辑,统一处理,这充分体现了面向对象
的思想。
这一类 clock 具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的 clk_ops 回调函数,是最简单的一类clock。可以直接通过 DTS 配置的方式支持,clock framework core 能直接从DTS中解出clock信息,并自动注册到kernel,不需要任何 driver 支持。
但是在 qucomC 中这类 clk 的注册不是通过 dts完成的,是首先将这些clk定义在drivers/clk/tiger/clk-qucom.c中的相关表格中,然后调用对应的注册API完成注册。
clock framework 使用 struct clk_fixed_rate
结构抽象这一类 clock,另外提供了一个接口,可以直接注册fixed rate clock,如下:
struct clk_fixed_rate {
struct clk_hw hw;
unsigned long fixed_rate;
u8 flags;
};
extern const struct clk_ops clk_fixed_rate_ops;
struct clk *clk_register_fixed_rate(struct device *dev,
const char *name,
const char *parent_name,
unsigned long flags,
unsigned long fixed_rate);
clock provider 一般不需要直接使用 struct clk_fixed_rate 结构,因为clk_register_fixed_rate接口是非常方便的;clk_register_fixed_rate
接口以 clock name、parent name、fixed_rate为参数,创建一个具有固定频率的clock,该clock的 clk_ops 也是clock framework提供的,不需要 provider 关心;
目前 qucomC 中使用的就是这种方式。如果使用DTS的话,clk_register_fixed_rate 都不需要,直接在DTS中配置即可。
struct tiger_param_fixed_rate_clk {
unsigned int id; //clk table中的索引值
char *name;
const char *parent_name; //parent_name 都是为NULL
unsigned long flags; //为 0
unsigned long fixed_rate;
};
static struct tiger_param_fixed_rate_clk fixed_rate_clks[] = {
{QUCOM_CLK_CLK32, "clk32", NULL, 0, 32768},
{QUCOM_CLK_VCTCXO, "vctcxo", NULL, 0, 26000000},
{QUCOM_CLK_VCTCXO_3P25M, "vctcxo_3p25", NULL, 0, 3250000},
{QUCOM_CLK_VCTCXO_1M, "vctcxo_1", NULL, 0, 1000000},
{QUCOM_CLK_PLL1_VCO, "pll1_2496_vco", NULL, 0, 2496000000},
};
__init qucom_clk_init(struct device_node *np)
-->qucom_pll_init(struct qucom_clk_unit *tiger_unit)
-->tiger_register_fixed_rate_clks((struct tiger_clk_unit *unit)
这一类 clock 具有固定的 factor(即multiplier和divider),clock 的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。
struct tiger_param_fixed_factor_clk {
unsigned int id; //clk id
char *name;
const char *parent_name;
unsigned long mult; //倍频因子,目前tiger中都为1
unsigned long div; //分频因子
unsigned long flags; //tiger中大部分0,有些为CLK_SET_RATE_PARENT
};
由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供.recalc_rate/.set_rate/.round_rate
等回调。
const struct clk_ops clk_fixed_factor_ops = {
.round_rate = clk_factor_round_rate,
.set_rate = clk_factor_set_rate,
.recalc_rate = clk_factor_recalc_rate,
};
可通过下面接口注册这类clk:
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);
另外,这一类接口和fixed rateclock类似,不需要提供driver,只需要配置dts即可
tiger 不是采用dts
1)首先定义clk_register_fixed_factor 所需要的各种参数定义表格
static struct tiger_param_fixed_factor_clk fixed_factor_clks[] = {
{QUCOM_CLK_PLL1_D1_2496_VCO, "pll1_d1_2496_vco","pll1_2496_vco", 1, 1, 0},
{QUCOM_CLK_PLL1_D2_1248_VCO, "pll1_d2_1248_vco","pll1_2496_vco", 1, 2, 0},
{QUCOM_CLK_PLL1_D3_832_VCO, "pll1_d3_832_vco", "pll1_2496_vco", 1, 3, 0},
{QUCOM_CLK_PLL1_D4_624_VCO, "pll1_d4_624_vco", "pll1_2496_vco", 1, 4, 0},
{QUCOM_CLK_PLL1_D5_499_VCO, "pll1_d5_499_vco", "pll1_2496_vco", 1, 5, 0},
tiger_register_fixed_factor_clks(struct tiger_clk_unit *unit,...
这一类 clock 只可开关(会提供.enable/.disable
回调),drivers/clk/clk-gate.c
const struct clk_ops clk_gate_ops = {
.enable = clk_gate_enable, //--> clk_read/clk_write
.disable = clk_gate_disable,
.is_enabled = clk_gate_is_enabled,
};
可使用下面接口注册:
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);
需要提供的参数包括:
name: clock 的名称;
parent_name: parent clock的名称,没有的话设置为NULL;
flag: 可参考3.1中的说明;
reg: 控制该clock开关的寄存器地址(虚拟地址);
bit_idx: 控制 clock 开关的bit位(是1开,还是0开,可通过下面gate特有的flag指定);
clk_gate_flags: gate clock特有的flag,当前只有一种:CLK_GATE_SET_TO_DISABLE,clock开关控制的方式,如果置位,表示写1关闭clock,反之亦然;
lock: 如果clock开关时需要互斥,可提供一个spinlock。
struct tiger_param_general_gate_clk {
unsigned int id;
const char *name;
const char *parent_name;
unsigned long flags;
unsigned long offset;
u8 bit_idx;
unsigned long gate_flags;
spinlock_t *lock;
};
static struct tiger_param_general_gate_clk general_gate_clks[] = {
{QUCOM_CLK_PLL2_D1, "pll2_d1", "pll2_d1_vco", 0, APB_SPARE_PLL2CR, 26, 0, &pll2_lock},
{QUCOM_CLK_PLL2_D2, "pll2_d2", "pll2_d2_vco", 0, APB_SPARE_PLL2CR, 27, 0, &pll2_lock},
{QUCOM_CLK_PLL2_D3, "pll2_d3", "pll2_d3_vco", 0, APB_SPARE_PLL2CR, 28, 0, &pll2_lock},
{QUCOM_CLK_PLL2_D4, "pll2_d4", "pll2_d4_vco", 0, APB_SPARE_PLL2CR, 29, 0, &pll2_lock},
{QUCOM_CLK_PLL2_D5, "pll2_d5", "pll2_d5_vco", 0, APB_SPARE_PLL2CR, 30, 0, &pll2_lock},
struct clk *clk_register_gate
注册AP tiger_register_general_gate_clk
进行注册,封装主要是为了方便注册,比如将统一类型的clk注册信息填入一个数组中,让后直以数组为参数进行注册,这样减少了代码量。#define MPMU_WDTPCR 0x200 //watchdog clk 控制寄存器的偏移地址
//注册 watchdog clk
clk = tiger_clk_register_gate(NULL, "WDT_CLK", "pll1_13_wdt", 0,
tiger_unit->mpmu_base + MPMU_WDTPCR, 0x7, 0x3, 0x4, 0, NULL);
tiger_clk_add(unit, QUCOM_CLK_WDT, clk);
在drivers/clk/tiger/clk-qucom.c文件中有上面几行代码,即为注册watchdog clk的流程,参数解释如下:
u32 mask: 在开关clk时,只操作控制寄存器的mask位,如mask为0x7==0b0111即只操作控制寄存器的0-2位,具体方法及对0-2进行清空,然后在赋值。
u32 val_enable:对控制寄存器的0-2位先清空在或上val_enable即可打开该clk
u32 val_disable: 对控制寄存器的0-2位先清空在与上val_disable即可关闭该clk
viod __iomem *reg
: watchdog控制寄存器的物理地址,可以看出是从mpmu基地址开始偏移的。
这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate
回调),drivers/clk/clk-divider.c
const struct clk_ops clk_divider_ops = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_divider_round_rate,
.set_rate = clk_divider_set_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);
该接口用于注册分频比规则的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(不改变,视硬件支持而定)。
如有需要其他分频方式,就需要使用另外一个接口,如下:
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);
该接口用于注册分频比不规则的 clock,和上面接口比较,差别在于divider 值和寄存器值得对应关系由一个table决定,该table的原型为:
struct clk_div_table {
unsigned int val;
unsigned int div;
};
其中val表示寄存器值,div表示分频值,它们的关系也可以通过clk_divider_flags改变。
目前 tiger 上封装了下面函数,但是并没有使用。
void tiger_register_div_clks(struct tiger_clk_unit *unit,
struct tiger_param_div_clk *clks,
void __iomem *base, int size)
这一类 clock可以选择多个parent,因为会实现 .get_parent/.set_parent/.recalc_rate回调,linux/drivers/clk/clk-mux.c
const struct clk_ops clk_mux_ops = {
.get_parent = clk_mux_get_parent,
.set_parent = clk_mux_set_parent,
.determine_rate = clk_mux_determine_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);
该接口可注册 mux 控制比较规则的clock(类似divider clock):
parent_names: 一个字符串数组,用于描述所有可能的parent clock;
num_parents:parent clock的个数;
reg、shift、width:选择 parent 的寄存器、偏移、宽度,默认情况下,寄存器值为 0 时,对应第一个parent,依此类推。如有例外,可通过下面的flags,以及另外一个接口实现;
clk_mux_flags:mux clock特有的flag(include/linux/clk-provider.h):
CLK_MUX_INDEX_ONE,寄存器值不是从0开始,而是从1开始;
CLK_MUX_INDEX_BIT,寄存器值为2的幂。
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);
该接口通过一个table,注册mux控制不规则的clock,原理和divider clock类似,不再详细介绍。
mux 只是用来切换parent,所以只需要定义一个数组填写上该clk的parent既可。
static const char * const uart_parent_names[] = {"pll1_58p5", "uart_pll"};
static const char * const ssp_parent_names[] = {"pll1_6p5", "pll1_13",
"pll1_26", "pll1_52"};
static const char *timer_parent_names[] = {"pll1_13", "clk32",
"pll1_6p5", "vctcxo_3p25", "vctcxo_1"};
顾名思义,就是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);
本节将深入到 clock framework 的内部,分析相关的实现逻辑。
clock provider 需要将系统的 clock 以 tree 的形式组织起来,分门别类,并在系统初始化时,通过provider的初始化接口,或者clock framework core的 DTS接口,将所有的clock注册到 kernel。下面对clk 注册函数clk_register分节进行介绍。
step_1,step_2 根据 struct clk_hw 指针提供的信息,初始化 clk 的 name、ops、hw、flags、num_parents、parents_names等变量。
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
int i, ret;
struct clk_core *core;
core = kzalloc(sizeof(*core), GFP_KERNEL);----------->1
core->name = kstrdup_const(hw->init->name, GFP_KERNEL);--->2
core->ops = hw->init->ops;
if (dev && dev->driver)
core->owner = dev->driver->owner;
core->hw = hw;
core->flags = hw->init->flags;
core->num_parents = hw->init->num_parents;
core->min_rate = 0;
core->max_rate = ULONG_MAX;
hw->core = core;
FIXME: 关于clk parent_names is __initdata 的内容部分待完成…
/* allocate local copy in case parent_names is __initdata */
core->parent_names =
kcalloc(core->num_parents, sizeof(char *),GFP_KERNEL);
/* copy each string name in case parent_names is __initdata */
for (i = 0; i < core->num_parents; i++)
core->parent_names[i] =
kstrdup_const(hw->init->parent_names[i], GFP_KERNEL);
每次调用 get_clk 接口都会创建一个clk实例,并将其加入此struct clk_core哈希表中,每个clock 由一个 struct clk_core描述,其与 struct clk_hw 是一 一对应的关系,但是 struct clk可能有很多个,其他驱动需要操作 clock时,都需要先分配一个 struct clk 类型的指针,因此其与struct clk_core是一对多的关系,也可以说 clk 是 clk_core 的实例
INIT_HLIST_HEAD(&core->clks); ------->3
step_4: 分配一个 struct clk 结构并挂在hw->core->clks上, 然后返回clk指针,
hw->clk = __clk_create_clk(hw, NULL, NULL);-------->4
note: 该struct clk的定义参考drivers/clk/clk.c中的定义,如下:
#define CREATE_TRACE_POINTS
#include
struct clk {
struct clk_core *core;
const char *dev_id;
const char *con_id;
unsigned long min_rate;
unsigned long max_rate;
struct hlist_node clks_node;
};
setp_6:
ret = __clk_init(dev, hw->clk);------------------6
if (!ret)
return hw->clk; //返回clk
__clk_free_clk(hw->clk);
hw->clk = NULL;
}
调用内部接口 __clk_init,执行后续的初始化操作。这个接口包含了clk_regitser的主要逻辑,具体如下。
static int __clk_init(struct device *dev, struct clk *clk_user)
{
int i, ret = 0;
struct clk_core *orphan;
struct hlist_node *tmp2;
struct clk_core *core;
unsigned long rate;
/*在drivers/clk/clk.c对struct clk的定义中有成员 struct clk_core *core*/
core = clk_user->core;
clk_prepare_lock();
check to see if a clock with this name is already registered
clock framework以name唯一识别一个clock
if (clk_core_lookup(core->name))
goto out;
check that clk_ops are sane. See Documentation/clk.txt
检查 clk ops 的完整性,例如:如果提供了set_rate 接口,就必须提供 round_rate 和 recalc_rate接口
如果提供了set_parent,就必须提供get_parent。这些逻辑背后的含义,会在后面相应的地方详细描述;
if (core->ops->set_rate &&
!((core->ops->round_rate || core->ops->determine_rate)
&& core->ops->recalc_rate))
goto out;
if (core->ops->set_parent && !core->ops->get_parent)
goto out;
if (core->ops->set_rate_and_parent &&
!(core->ops->set_parent && core->ops->set_rate))
goto out;
throw a WARN if any entries in parent_names are NULL
分配一个 struct clk 类型的数组,缓存该clock的parents clock。具体方法是根据parents_name,查找相应的struct clk指针;
for (i = 0; i < core->num_parents; i++)
if (core->num_parents > 1 && !core->parents) {
core->parents = kcalloc(core->num_parents, sizeof(struct clk *)
if (core->parents)
for (i = 0; i < core->num_parents; i++)
core->parents[i] =
clk_core_lookup(core->parent_names[i]);
}
获取当前的 parent clock,并将其保存在parent指针中
core->parent = __clk_init_parent(core);
根据该 clock 的特性,将它添加到下面三个链表中的一个:
clk_root_list
和 clk_orphan_list
。所有设置了 CLK_IS_ROOT
属性的clock都会挂在clk_root_list
中。其它 clock:clk_orphan_list
中。查询时(__clk_lookup
接口做的事情),依次搜索即可:
clk_root_list
-->root_clk
-->children
-->child's children,
*clk_orphan_list
-->orphan_clk
-->children
-->child's children
if (core->parent) {
hlist_add_head(&core->child_node, &core->parent->children);
core->orphan = core->parent->orphan;
} else if (core->flags & CLK_IS_ROOT) {
hlist_add_head(&core->child_node, &clk_root_list);
core->orphan = false;
} else {
hlist_add_head(&core->child_node, &clk_orphan_list);
core->orphan = true;
}
此处应该是设置精度,但是从我们的底层驱动看,这个都是设置为默认值
if (core->ops->recalc_accuracy)
core->accuracy = core->ops->recalc_accuracy(core->hw,
__clk_get_accuracy(core->parent));
else if (core->parent)
core->accuracy = core->parent->accuracy;
else
core->accuracy = 0;
设置相位,默认为0
if (core->ops->get_phase)
core->phase = core->ops->get_phase(core->hw);
else
core->phase = 0;
计算 clock 的初始 rate:
对于提供 .recalc_rate ops的clock来说,优先使用该ops 获取初始的rate。
如果没有提供,退而求其次,直接使用parent clock的rate。
最后,如果该clock没有parent,则初始的rate只能选择为0。
.recalc_rate ops
的功能,是以parent clock的rate为输入参数,根据当前硬件的配置情况,如寄存器*值,计算获得自身的rate值。
if (core->ops->recalc_rate)
rate = core->ops->recalc_rate(core->hw,
clk_core_get_rate_nolock(core->parent));
else if (core->parent)
rate = core->parent->rate;
else
rate = 0;
core->rate = core->req_rate = rate;
有些情况下,child clock会先于parent clock注册,此时该child 就会成为 orphan clock,被收养在clk_orphan_list中。而每当新的 clock 注册时,kernel都会检查这个clock是否是某个orphan 的parent,如果是,就把这个orphan从clk_orphan_list中移除,放到新注册的clock的怀抱。这就是reparent的功能,它的处理逻辑是:
/*walk the list of orphan clocks and reparent any that are */
/* children of this clock 参考说明4 */
hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
if (orphan->num_parents && orphan->ops->get_parent) {
i = orphan->ops->get_parent(orphan->hw);
if (i >= 0 && i < orphan->num_parents &&
!strcmp(core->name, orphan->parent_names[i]))
clk_core_reparent(orphan, core);
continue;
}
for (i = 0; i < orphan->num_parents; i++)
if (!strcmp(core->name, orphan->parent_names[i])) {
clk_core_reparent(orphan, core);
break;
}
}
如果clock ops提供了init接口,执行之 ,如无特殊硬件层面的限制,不建议实现此init函数
if (core->ops->init)
core->ops->init(core->hw);
kref_init(&core->ref);
}
clock get 是通过clk name 获取 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 为例,分析其实现过程(位于drivers/clk/clkdev.c中)。
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) {
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);
}
EXPORT_SYMBOL(clk_get);
如果提供了 struct device 指针,则调用 of_clk_get_by_name 接口,通过device tree接口获取clock指
针。否则,如果没有提供设备指针,或者通过 device tree 不能正确获取clock,则进一步调用clk_get_sys。
clock consumer (需要使用时钟的设备)会在本设备的 DTS中,以clocks、clock-names为关键字,定义所需的clock,下面 例-2.3.3 可以看到有的node 有参数clock-names有的node没有clock-names,没有clock-names的node,对于没有clock-names其driver的probe函数通过dev->of_node获取 驱动设备节点中填写的clk ID,然后再根据这个ID,获取clk handle。流程如下 例-2.3.2。
clk_get(struct device *dev, const char *con_id) //con_id为null
-->__of_clk_get_by_name
-->__of_clk_get
-->__of_clk_get_from_provider
/* 遍历说有clk provider */
-->list_for_each_entry(provider, &of_clk_providers, link)
--> __clk_create_clk //创建clk handle 并返回
例-2.3.2
watchdog: watchdog@d4080000 {
compatible = "tiger,soc-wdt";
clocks = <&soc_clocks QUCOM_CLK_WDT>;
reg = <0xd4080000 0xff>,
<0xd4050000 0x1024>;
status = "disabled";
};
aux_adc: aux_adc@d409010c {
compatible = "tiger,auxadc";
#io-channel-cells = <1>;
io-channel-ranges;
clocks = <&soc_clocks QUCOM_CLK_THERMAL>;
clock-names = "tiger_auxadc_clk";
reg = <0xd409010c 0x4>,
<0xd4013380 0x20>,
<0xd40133F0 0x4>;
status = "ok";
};
static struct clk *__of_clk_get_by_name(struct device_node *np,
const char *dev_id, const char *name)
{
struct clk *clk = ERR_PTR(-ENOENT);
/* Walk up the tree of devices looking for a clock that matches */
while (np) {
int index = 0;
if (name)
index = of_property_match_string(np, "clock-names", name);
clk = __of_clk_get(np, index, dev_id, name);
if (!IS_ERR(clk)) {
break;
} else if (name && index >= 0) {
if (PTR_ERR(clk) != -EPROBE_DEFER)
return clk;
}
/* No matching clock found on this node. If the parent node
* has a "clock-ranges" property, then we can try one of its
* clocks.*/
np = np->parent;
if (np && !of_get_property(np, "clock-ranges", NULL))
break;
}
return clk;
}
整个 clk 注册以时间为顺序进行分析,首先在 kernel 启动时会调用 of_clk_init 函数,该函数解析和 clk相关的设备节点,在of_clk_init
函数执行之前,系统已经完成了device tree的初始化,因此系统中的所有的设备节点都已经形成了一个树状结构,每个节点代表一个设备的 device node
。of_clk_init 是针对系统中的所有的 device node,扫描 __clk_of_table
,进行匹配,一旦匹配到,就调用该clk driver的初始化函数,
of_clk_init 负责从DTS中扫描并初始化 clock provider,该接口有一个输入参数,用于指定需要扫描的 ”struct of_device_id *matches
“;如 matches为空,则会扫描 3.1节中的 __clk_of_table
, 通过CLK_OF_DECLARE
定义的__of_table_qucom_clk
等类型的clock。
void __init of_clk_init(const struct of_device_id *matches)
{
struct device_node *np;
if (!matches)
matches = __clk_of_table; //见第3.2节内容
for_each_matching_node(np, matches) {
const struct of_device_id *match =
of_match_node(matches, np);
clk_init_cb(np);
}
}
在最新的 kernel中,在linux/init/main.c函数 asmlinkage__visible void __init start_kernel() 中调time_init()并以NULL为参数调用一次of_clk_init(NULL),以便自动匹配并初始化DTS中的描述的类似fixed rate的clock。
这里为:qucom_clk_init(struct device_node *np));并把硬件的 device node作为参数传递给clk driver,
系统的 clock 资源 clock provider 的DTS怎么写。通常有两种方式:
第一种方式:见3.3节。
第二种方式:系统将所有 clock provider 设备抽象为一个虚拟设备,在qucomC DTS中所有clock provider 都定义于节点“soc_clocks: clocks
”中,将系统所有的clock provider 抽象为一个虚拟的设备,用一个DTS node表示。
note:“tiger, qucom-clock
"位于文件.arch/arm64/boot/dts/tiger/qucom-common.dtsi
*这个虚拟的设备称作clock controller,
soc_clocks: clocks{
compatible = "tiger,qucom-clock";
reg = <0x0 0xd4050000 0x0 0x209c>, //寄存器的基地址与长度
<0x0 0xd4282800 0x0 0x400>,
<0x0 0xd4015000 0x0 0x1000>,
<0x0 0xd4090000 0x0 0x1000>,
<0x0 0xd4282c00 0x0 0x400>,
<0x0 0xd8440000 0x0 0x98>,
<0x0 0xd4200000 0x0 0x4280>;
reg-names = "mpmu", "apmu", "apbc", "apbs",
"ciu", "dciu", "ddrc";
interrupts = <0 IRQ_QUCOM_AP_PMU IRQ_TYPE_LEVEL_HIGH>;
#clock-cells = <1>;
};
clock-cells:该clock的cells,“1” 表示该clock有多个输出,clock consumer需要通过 ID 值指定所要使用的clock(很好理解,系统那么多clock,被抽象为1个设备,因而需要额外的ID标识)。
在 dts文件中需要根据设备所在的总线来定义设备节点。如,总的结点是 “soc {”, AXI设备是SoC下一级,所以需要挂在 SoC 结点下面,而 sdh又是挂在于 AXI 下面的设备,所以 sdh 设备节点需要定义于 AXI 设备节点下面。这里需要知道的是,不管是 AXI 总线还是 APB 总线他们只是数据的传输设备,所以他们有自己的控制器,ASIC设计时,会将AXI、APB的controller的I/O(寄存器地址)映射到cpu的地址总线上。
*设备节点层级关系:
soc {
axi@d4200000 { /* AXI */
sdh0: sdh@d4280000 {
gpu: gpu@c0500000 {
apb@d4000000 { /* APB */
sulog: sulog@d40b0100 {
watchdog: watchdog@d4080000 {
twsi0: i2c@d4011000 {
当具体的设备(clock consumer)需要打开/关闭某个clock时,在编写驱动时首先需要在dts中指明该设备使用哪个clock,当前qucom-common.dtsi
中"#clock-cells"
为1, 所以需要在驱动的dts中指明具体需要使用哪个 clock 输出。在AquilC中将系统所有的clock的 ID,定义在一个头文件中(include/dt-bindings/clock/tiger-qucom.h
),而 ”qucom-common.dtsi
“ 包含这个头文件, 如下所示:
#include
#include
#include
#include
#include
#include
#include
#include
下面例子使用"tiger-qucom.h
"中clock ID的宏定义“clocks = <&soc_clocks QUCOM_CLK_CLST0>
”。
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0 0x0>;
enable-method = "psci";
cpu-idle-states = <&CPU_C2 &CLUSTER_MP2 &CHIP_D1P &CHIP_D1>;
sched-energy-costs = <&CPU_COST_0 &CLUSTER_COST_0>;
clocks = <&soc_clocks QUCOM_CLK_CLST0>;
operating-points-v2 = <&clst0_core_opp_table>;
};
clocks:指明该设备的 clock 列表为 “soc_clocks”,clk_get
时,会以它为关键字,去device_node中搜索,以得*到对应的struct clk指针。
首先,在代码编写阶段,会在 clk-qucom.c 中通过 “CLK_OF_DECLARE(qucom_clk, "tiger, qucom-clock", qucom_clk_init)
” 宏来定义并初始化一个 of_device_id
类型的结构体,
CLK_OF_DECLARE(qucom_clk, "tiger,qucom-clock", qucom_clk_init);
该宏定义最终解析成下面内容:
static const struct of_device_id __of_table_qucom_clk =
__used __section(__clk_of_table)
{
//of_clk_init 会以该属性进行匹配
.compatible = "tiger,qucom-clock",
.data = qucom_clk_init //匹配成功后会调用该函数
}
注册驱动初始化函数主要是进行相关clk注册前的准备工作,如获取寄存器的地址等
CLK_OF_DECLARE
(include/linux/clk-provider.h”) 就是初始化了一个 “struct of_device_id
” 的静态常量,并放置在"__clk_of_table section
"中。
上面这一段需要借助内核编译的 ”lds
“ 文件来解读,其中 "__used __section(__clk_of_table)
"传入的“__clk_of_table
” 参数用来给编译器确定常量“ __of_table_qucom_clk
”的存放位置。
_section(__clk_of_table )
意思就是把 “__of_table_qucom_clk
” 存入 “__clk_of_table
"段中(在 lds 文件中 “__clk_of_table
” 段是以__clk_of_table_end
为结尾)。 由上面的定义可以知道,在编译内核的时候,会把所有的__clk_of_table##nam
e变量都保存在”__clk_of_table
"中。
重点来了:“__clk_of_table
” 就是内核.drivers/clk/clk.c(3.1.1节内容)中的 “__clk_of_table
”,这个table保存了kernel支持的所有的clock driver的ID信息(用于和device node的匹配)。
of_device_id
结构体内容如下:
/* Struct used for matching a device */
struct of_device_id {
char name[32]; //要匹配的device node的名字
char type[32]; //要匹配的device node的类型
/*用来匹配适合的device node这里为:"tiger,qucom-clock",*/
char compatible[128];
/*存储初始化函数指针,为“qucom_clk_init(struct device_node *np)”*/
const void *data;
};
”struct of_device_id“ 结构是用来进行 device node 和 driver模块进行匹配用的。在初始化该结构时,会初始化该结构的以下两个重要的成员:
1)compatible:of_clk_init 函数在进行匹配时需要依据 “属性名” 来匹配,of_clk_init会到设备树中遍历设备节点,根据设备节点的属性 “compatible” 来进行匹配,在qucomC中该值为:“tiger, qucom-clock”
2)data: 在匹配成功后,就会调用注册驱动初始化函数进行后续处理,data成员即为函数指针 ,
在qucom.c中会调用"qucom_clk_init"函数。
在3.1.1 节中当调用 of_clk_init()
函数解析设备树并匹配 " tiger, qucom-clock
"成功后,会调用调用CLK_OF_DECLARE(qucom_clk, "tiger, qucom-clock", qucom_clk_init)
” 中的 qucom_clk_init
, 并在该函数中调用各种 tiger 封装的 clk 各种注册API,完成各种clk的注册,如ddr、cpu,camera,watchdog等 clk 的注册。由于qucom_clk_int
函数内容较多,接下来分为几段进行介绍。
在驱动中 qucom_clk_unit 结构体中包含了mpmu, apmu, apbc, apbs, ciu, dciu, ddrc的基地址,clk相关的寄存器都是从这些基地址开始,所以开始会在函数 “qucom_clk_init” 中初始化结构体 qucom_clk_unit ,该结构体的地址最后会赋值给全局变量" global_tiger_unit"。
struct qucom_clk_unit {
struct tiger_clk_unit unit;
void __iomem *mpmu_base;
void __iomem *apmu_base;
void __iomem *apbc_base;
void __iomem *apbs_base;
void __iomem *ciu_base;
void __iomem *dciu_base;
void __iomem *ddrc_base;
u32 irq;
struct completion fc_complete;
};
在初始化阶段只要获取 struct qucom_clk_unit tiger_unit 即可以获取所有clk寄存器的地址。后续阶段可以通过全局变量 “global_tiger_unit” 来对clk进行控制。
static void __init qucom_clk_init(struct device_node *np),
{
struct qucom_clk_unit *tiger_unit;
regs_addr_iomap();
/* 所有注册的clk都可以在 一张表中查到,该表就位于:
/* tiger_unit结构体中的struct clk **clk_table;
tiger_unit = kzalloc(sizeof(*tiger_unit), GFP_KERNEL);
/* 从clk设备节点中解析出相关外设控制寄存器的基地址*/
tiger_unit->mpmu_base = of_iomap(np, 0); //获取第一个基地址
tiger_unit->apmu_base = of_iomap(np, 1); //np:clk虚拟clock设备节点,
....
在函数 qucom_clk_init 中会根据 “include/dt-bindings/clock/tiger-qucom.h” 定义注册的 clk 个数(qucom_NR_CLKS) 为 struct qucom_clk_unit tiger_unit–>clk_table 分配空间, 其他驱动调用 clk_get 接口时最终也是先到该表中查找到注册时注册的clk,再以该 clk 为母本重新复制一份。
tiger_clk_init(np, &tiger_unit->unit, QUCOM_NR_CLKS);
-->of_clk_add_provider(np, of_clk_src_onecell_get,
&unit->clk_data)
该表中的索引值定义位于”include/dt-bindings/clock/tiger-qucom.h“,其他设备在在dts文件中会指明使用的索引值。
定义watch dog索引值:
#define QUCOM_CLK_WDT 281
dts中使用
watchdog: watchdog@d4080000 {
compatible = "tiger,soc-wdt";
clocks = <&soc_clocks QUCOM_CLK_WDT>;
reg = <0xd4080000 0xff>,
<0xd4050000 0x1024>;
status = "disabled";
};
注册杂项的clk, 如 LCD, 这种clk的注册可以没有调用clk通用API接口,有的是直接对寄存器进行操作。如,调用clk_readl、clk_writel等函数完成对clk的操作
qucom_misc_init(tiger_unit)
;
在下面函数中完成 pll clk注册以及固定频率的clk注册
qucom_pll_init(tiger_unit);
-->tiger_register_fixed_rate_clks //不需要加入clk_table中
-->tiger_register_fixed_factor_clks //不需要加入clk_table中
有些 clk 的值是固定,不能改变的,如32K 时钟clk,以及26M VCXO 时钟他们的clk是固定的,如
drivers/clk/tiger/clk-qucom.c 中的 fixed_rate_clks[] 表中描述。此外,可以看到 vcxo 的时钟有好几种选择:26M,3.25M,1M。需要注意的是这些clk没有enable 和disable的接口函数,上电clk就会开,下电clk就会关掉。
static struct tiger_param_fixed_rate_clk fixed_rate_clks[] = {
{QUCOM_CLK_CLK32, "clk32", NULL, 0, 32768},
{QUCOM_CLK_VCTCXO, "vctcxo", NULL, 0, 26000000},
{QUCOM_CLK_VCTCXO_3P25M, "vctcxo_3p25", NULL, 0, 3250000},
{QUCOM_CLK_VCTCXO_1M, "vctcxo_1", NULL, 0, 1000000},
{QUCOM_CLK_PLL1_VCO, "pll1_2496_vco", NULL, 0, 2496000000},
};
3)
qucom_clocks_enable(bootup_on_clocks_tbl,
ARRAY_SIZE(bootup_on_clocks_tbl));
if (cpu_is_qucomc())
qucom_clocks_enable(qucomc_bootup_on_clocks_tbl,
ARRAY_SIZE(qucomc_bootup_on_clocks_tbl));
/* 注册外设的clk,watchdog clk就包含其中apb总线 */
qucom_apb_periph_clk_init(tiger_unit);
qucom_axi_periph_clk_init(tiger_unit); //注册外设的 axi总线
qucom_camera_clk_init(tiger_unit); //注册cameraclk
#ifdef CONFIG_tiger_CORE_CLK
qucom_acpu_init(tiger_unit); //注册cpu clk pll3
qucomc_ddr_init(tiger_unit); //注册ddr clk
/* enable pmuap interrupt */
enable_dfc_int(tiger_unit->apmu_base, true);
#endif
qucom_dummy_clk_init(tiger_unit);
/*去打开数组“keep_on_clocks_tbl[]”中描述的clocks*/
qucom_clocks_enable(keep_on_clocks_tbl,
ARRAY_SIZE(keep_on_clocks_tbl));
#ifdef CONFIG_tiger_CLK_DCSTAT
globla_tiger_unit = tiger_unit;
#endif
return;
}
CLK_OF_DECLARE(qucom_clk, “tiger,qucom-clock”, qucom_clk_init);
在3.3.3 节中先会使用 tiger自定义的接口,然后在自定义的接口里面调用kernel clk 接口来完成注册,最后调用tiger_clk 将注册好的 clk 加入clk_table表中。
qucom_clk_init
-->qucom_axi_periph_clk_init
-->tiger_clk_register_gate
-->tiger_clk_add(unit, qucom_CLK_WDT, clk);
-->tiger_unit->clk_table[id] = clk;
soc_clocks: clocks@d4050000{
compatible = "tiger,qucom-clock";
reg = <0x0 0xd4050000 0x0 0x209c>,
<0x0 0xd4282800 0x0 0x400>,
<0x0 0xd4015000 0x0 0x1000>,
<0x0 0xd4090000 0x0 0x1000>,
<0x0 0xd4282c00 0x0 0x400>,
<0x0 0xd8440000 0x0 0x98>,
<0x0 0xd4200000 0x0 0x4280>;
reg-names = "mpmu", "apmu", "apbc", "apbs",
"ciu", "dciu", "ddrc";
interrupts = <0 IRQ_QUCOM_AP_PMU IRQ_TYPE_LEVEL_HIGH>;
#clock-cells = <1>;
};
在 qucom_clk_init 函数中首先会调用of_iomap获取mpmu的基地址:
tiger_unit->mpmu_base = of_iomap(np, 0);
参数 struct device_node np 即为抽象的clk虚拟设备 (dts中soc_clocks: clocks节点)。如下为 of_iomap 函数的
of_iomap定义:
void __iomem *of_iomap(struct device_node *np, int index)
device_node *np
: 为soc_clocks: clocks节点,
int index: 为soc_clocks: clocks节点中reg-names属性中成员的索引。如index为0 即对应mpmu,
note: of_iomap函数中会调用函数:of_address_to_resource(np, index, &res)
。
Int of_address_to_resource(struct device_node *dev, int index,
struct resource *r)
of_get_address - 提取I/O口地址
np - 设备节点指针
index - 地址的标号
size - 输出参数,I/O口地址的长度
flags - 输出参数,类型(IORESOURCE_IO、IORESOURCE_MEM)
注意: 最关键函数出现了,
of_property_read_string_index(dev, “reg-names”, index, &name)
在该函数中在 ”soc_clocks: clocks“ 节点中根据属性:“reg-names” 按照索引值 index, 将reg-names = “mpmu”, “apmu”, “apbc”, “apbs”, “ciu”, “dciu”, “ddrc”;中的某一成员写到&name中。
后续解析
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中,方便后续的查找使用。该接口会在后面再详细介绍。
每一个可输出clock的器件,如Oscillator、PLL、Mux等都是一个设备,用一个DTS node表示。每一个器件,即是clock provider,也是clock consumer(根节点除外,如OSC),因为它需要接受clock输入,经过处理后,输出clock。参考如下例子,qucom并没有使用该方法
/* arch/arm/boot/dts/sun4i-a10.dtsi */
clocks {
#address-cells = <1>;
#size-cells = <1>;
ranges;
/*
* This is a dummy clock, to be used as placeholder on
* other mux clocks when a specific parent clock is not
* yet implemented. It should be dropped when the driver
* is complete.
*/
dummy: dummy {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <0>;
};
osc24M: osc24M@01c20050 {
#clock-cells = <0>;
compatible = "allwinner,sun4i-osc-clk";
reg = <0x01c20050 0x4>;
clock-frequency = <24000000>;
};
osc32k: osc32k {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <32768>;
};
pll1: pll1@01c20000 {
#clock-cells = <0>;
compatible = "allwinner,sun4i-pll1-clk";
reg = <0x01c20000 0x4>;
clocks = <&osc24M>;
};
/* dummy is 200M */
cpu: cpu@01c20054 {
#clock-cells = <0>;
compatible = "allwinner,sun4i-cpu-clk";
reg = <0x01c20054 0x4>;
clocks = <&osc32k>, <&osc24M>, <&pll1>, <&dummy>;
};
axi_gates: axi_gates@01c2005c {
#clock-cells = <1>;
compatible = "allwinner,sun4i-axi-gates-clk";
reg = <0x01c2005c 0x4>;
clocks = <&axi>;
clock-output-names = "axi_dram";
};
OSC26M和osc32k是两个root clock,因此只做clock provider功能。它们的cells均为0,因为直接使用名字即可引用。另外,增加了“clock-frequency”自定义关键字,这样在板子使用的OSC频率改变时,如变为12M,不需要重新编译代码,只需更改DTS的频率即可(这不正是Device Tree的核心思想吗!)。话说回来了,osc24M的命名不是很好,如果频率改变,名称也得改吧,clock consumer的引用也得改吧;
PLL1 即是clock provider(cell为0,直接用名字引用),也是clock consumer(clocks关键字,指定输入clock为“osc24M”);
再看一个复杂一点的,ahb_gates,它是clock provider(cell为1),通过clock-output-names关键字,描述所有的输出时钟。同时它也是clock consumer(由clocks关键字可知输入clock为“ahb”)。需要注意的是,clock-output-names关键字只为了方便clock provider编程方便(后面会讲),clock consumer不能使用(或者可理解为不可见);