【Linux Clock Framework】

文章目录

  • Introduction
  • 1.1 clock framwork
      • 1.1.1 总线时钟
      • 1.1.2 APB总线接口
      • 1.1.3 总线访问流程
      • 1.1.4 HW clock device tree
      • 1.1.5 clk register
    • 1.2 HW-specific Clock provider driver
      • 1.2.1 clk device 抽象 clk_hw
      • 1.2.2 struct clk_init_data
      • 1.2.3 struct clk_ops
      • 1.2.4 Driver 对 clk device的使用
      • 1.2.5 struct clk_core
      • 1.2.6 struct clk
    • 1.3 clk_register
      • 1.3.1 clk register 接口
    • 1.4 通用API的使用说明
      • 1.4.1 设备驱动DTS Clk 定义
      • 1.4.2 系统 CLK DTS解析
    • 1.5 clock分类及register
      • 1.5.1 fixed rate clock
      • 1.5.2 fixed factor clock
      • 1.5.3 gate clock
      • 1.5.4 divider clock
      • 1.5.5 mux clock
      • 1.5.6 composite clock
  • 2. clk_register & clk_get分析
      • 2.1.1.分配 struct clk_core空间
      • 2.1.2 创建哈希表头
      • 2.1.3 struct clk 分配
      • 2.1.4 __clk_init 调用完成注册
    • 2.2 clk_get实现
      • 2.3.2 __of_clk_get_by_name
  • 3. Clk DTS
    • 3.1 通过DTS注册clock
      • 3.1.1 of_clk_init 解析DTS
    • 3.2 clock provider DTS
      • 3.2.2 CLK_OF_DECLARE
      • 3.2.3 of_device_id
    • 3.3 clk 初始化函数-qucom_clk_init
      • 3.3.1 硬件基地址的获取
      • 3.3.2 clk table的分配
      • 3.3.3 各种clk注册
      • 3.3.4 clk 加入clk_table表中
    • 4.1 of_iomap
      • 4.1.1 of_address_to_resource
      • 4.1.2 __of_address_to_resource
      • 4.1.3 of_fixed_clk_setup
    • 4.2 clock provider DTS第二种写法

Introduction

kernel 称 clock driver 为 clock provider(相应的 clock 的使用者为clock consumer)。clock device是指那些能够产生clock信号、控制clock信号的设备,如:

  1. 用于产生clock 的 Oscillator(有源振荡器,也称作谐振荡器)或者Crystal(无源振荡器,也称晶振);
  2. 用于倍频的 PLL (锁相环,Phase Locked Loop);
  3. 用于分频的 divider;
  4. 用于多路选择的Mux;
  5. 用于clock enable控制的与门;
    使用clock的硬件模块(可称作consumer)。每一个clock device 都可以用一个DTS node表示,此外每一个器件即是 clock provider又是 clock consumer(根节点除外如: OSC), 因为它需要接受其它 clock 的输入,然后经过处理后再输出clock。

1.1 clock framwork

1.1.1 总线时钟

总线下面有device,如果device的频率高了,总线抗不住了就需要升频,如 APB 总线下面常常挂在一些外设,一些外设的时钟是由APB时钟分配出来的,外设需要提升数据传输速率,APB总线就需要提升clk, 也就是说SoC数据中的数据是通过APB总线与外设进行交互的。因为数据的收发是需要cpu先将数据放到移位寄存器中去,然后通过总线按bit发送出去,SoC中的一些controller的读写是通过什么总线进行操作的呢??
这个需要看设备接到哪了,比如i2c uart等接在了apb上,cpu准备好数据,发到axi,再到axi-to-apb,再到具体controller。

  • AXI、APB总线, SoC上电之后它们默认就可以使用。SoC片内 RAM 和和 ROM 的访问应该需要AXI总线。
  • AXI/APB时钟使用的是默认的, 等后续完成PLL配置之后,core才会去提升AXI/APB总线的时钟。

1.1.2 APB总线接口

PCLK APB总线时钟。
  PRESETn APB总线复位。低有效。
  PADDR 地址总线。
  PSELx 从设备选择。
  PENABLE APB传输选通。
  PWRITE 高为写传输,低为读。
  PRDATA 读数据总线。
  PWDATA 写数据总线。

1.1.3 总线访问流程

总线的简单通信过程是这样的:

  • 首先由 Master 发起一个请求,告诉总线控制器(带有 Arbiter 功能)我要掌握总线的控制权(因为可能有多个Master同时访问),
  • 总线控制器回给 Master一个同意控制的回答并且将Master的数据信号(比如address, write/read, writedata/readdata)接入总线,
  • 这些信号将会被所有 Slave收到。每个 Slave 都有一个自己的地址范围,如果 Master发出的地址是在某个slave的地址范围之内,总线控制器会通过一个select 信号告诉那个 Slave,你被选中了,你应该对总线上的 address, write/read…进行处理。所以每个 Slave里都会有一个FSM(状态机) 来处理这些总线信号(select, address, write/read…)

1.1.4 HW clock device tree

系统中的 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而已。

1.1.5 clk register

虽然 HW clock 的 datasheet 往往有clock gating 的内容(或者一些针对clock source进行分频的内容,概念类似), 但是,本质上负责 clock gating 的那个硬件模块(即对于clock的控制寄存器)需要独立出来,称为clock tree中的一个中间节点。而对中间节点的设定包括:

  • ON/OFF控制;
  • 频率设定、相位控制等;
  • 从众多输入的clock source中选择一个做为输出。

1.2 HW-specific Clock provider driver

这个模块是真正和系统中实际的 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 来表示,具体如下:

1.2.1 clk device 抽象 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则没有存在的意义了。

1.2.2 struct 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;
};
  • name: 该clock的名称;
  • ops: 该clock相关的操作函数集,具体参考1.2.3节内容的描述;
  • parent_names:该clock所有的parent clock的名称。这是一个字符串数组,保存了所有可能的parent;
  • num_parents :parent 的个数;
  • flags:一些 framework 级别的 flags,

1.2.3 struct clk_ops

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,
  • clk_prepare/clk_unprepare: 启动clock前的准备工作/停止clock后的善后工作。可能会睡眠。
  • clk_get_rate/clk_set_rate/clk_round_rate: clock频率的获取和设置,其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误。如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值。
  • 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接口则可放心。

1.2.4 Driver 对 clk device的使用

1)首先,在DTS(device tree source)中,指定device需要使用的clock,如下:

device {
     clocks = <&osc 1>, <&ref 0>;
     clock-names = "baud", "register";
};

该DTS的含义是:
device 需要使用两个 clock,“baud” 和 “regitser”,由 clock-names关键字指定;

  • baud 取自 “osc”的输出1,
  • register 取自 “ref” 的输出0,由clocks关键字指定。

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等操作。

1.2.5 struct clk_core

这个数据结构是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_root_list
  • clk_orphan_list。

所有设置了CLK_IS_ROOT 属性的 clock 都会挂在 clk_root_list 中,而这个链表中的每一个阶段又展成一个树状结构。(这和硬件拓扑是吻合的,clock tree 实际上是有多个根节点的,多条树状结构)。

其它clock,如果有valid 的 parent,则会挂到parent的“children”链表中,如果没有valid 的 parent,则会挂到 clk_orphan_list中。

1.2.6 struct clk

基本上,每一个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了。

1.3 clk_register

系统中,每一个clock 都有一个 struct clk_hw 变量描述,clock provider 需要使用 register相关的接口,将这些clock注册到kernel,clock framework的核心代码会把它们转换为struct clk变量,并以tree的形式组织起来。这些接口的原型如下:

1.3.1 clk register 接口

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都不需要关心。

1.4 通用API的使用说明

1.4.1 设备驱动DTS Clk 定义

首先,在DTS(device tree source)中,指定 device 需要使用的 clock,如,例-1.4 所示:

device {
   clocks = <&osc 1>, <&ref 0>;
   clock-names = "baud", "register";
};
例-1.4

该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.5

上面的 例-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 的句柄。

1.4.2 系统 CLK DTS解析

系统启动后,device tree 会解析 clock 有关的关键字,并将解析后的信息放在 platform_device 相关的字段中。

1.5 clock分类及register

根据 clock 的特点,clock framework将clock分为 6类:

  • fixed rate;
  • gate;
  • devider;
  • mux;
  • fixed factor;
  • composite。

每一类 clock 都有相似的功能、相似的控制方式,因而可以使用相同的逻辑,统一处理,这充分体现了面向对象的思想。

1.5.1 fixed rate 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中配置即可。

  • 首先定义clk_register_fixed_rate所需要的各种参数:
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;
};
  • 定义好需要注册的 fixed rate 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},
};
  • 调用tiger 封装的函数完成fixed clk的注册
__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)

1.5.2 fixed factor clock

这一类 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},
  1. 调用tiger 封装的函数完成fixed clk的注册
tiger_register_fixed_factor_clks(struct tiger_clk_unit *unit,...

1.5.3 gate clock

这一类 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。

  • 定义 tiger_register_general_gate_clks 所需要的参数
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},
  • 调用tiger封装的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基地址开始偏移的。

1.5.4 divider clock

这一类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)

1.5.5 mux clock

这一类 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"};

1.5.6 composite clock

顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:

struct clk *clk_register_composite(struct device *dev, const char *name,
       const char **parent_names, int num_parents,
       struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
       struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
       struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
       unsigned long flags);

2. clk_register & clk_get分析

本节将深入到 clock framework 的内部,分析相关的实现逻辑。

2.1.1.分配 struct clk_core空间

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

2.1.2 创建哈希表头

每次调用 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

2.1.3 struct clk 分配

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

2.1.4 __clk_init 调用完成注册

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
  • parent->children
    clock framework 有2条全局的链表:clk_root_listclk_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
    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的功能,它的处理逻辑是:

  • 遍历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设置类似。
/*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);
}

2.2 clk_get实现

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。

2.3.2 __of_clk_get_by_name

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";
};
例-2.3.3
系统启动后,device tree会简单的解析,以struct device_node指针的形式,保存在本设备的 of_node 变量中。而 __of_clk_get_by_name,就是通过扫描所有 “clock-names” 中的值,和传入的name比较,如果相同,获得它的index(即“clock-names”中的第几个),调用 of_clk_get,取得clock指针。
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;
}

3. Clk DTS

3.1 通过DTS注册clock

整个 clk 注册以时间为顺序进行分析,首先在 kernel 启动时会调用 of_clk_init 函数,该函数解析和 clk相关的设备节点,在of_clk_init 函数执行之前,系统已经完成了device tree的初始化,因此系统中的所有的设备节点都已经形成了一个树状结构,每个节点代表一个设备的 device node。of_clk_init 是针对系统中的所有的 device node,扫描 __clk_of_table,进行匹配,一旦匹配到,就调用该clk driver的初始化函数,

3.1.1 of_clk_init 解析DTS

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,

3.2 clock provider DTS

系统的 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指针。

3.2.2 CLK_OF_DECLARE

首先,在代码编写阶段,会在 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##name变量都保存在”__clk_of_table"中。

重点来了:“__clk_of_table” 就是内核.drivers/clk/clk.c(3.1.1节内容)中的 “__clk_of_table”,这个table保存了kernel支持的所有的clock driver的ID信息(用于和device node的匹配)。

3.2.3 of_device_id

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.3 clk 初始化函数-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函数内容较多,接下来分为几段进行介绍。

3.3.1 硬件基地址的获取

在驱动中 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设备节点,
   ....

3.3.2 clk table的分配

在函数 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";
 };

3.3.3 各种clk注册

  1. 注册杂项的clk, 如 LCD, 这种clk的注册可以没有调用clk通用API接口,有的是直接对寄存器进行操作。如,调用clk_readl、clk_writel等函数完成对clk的操作
    qucom_misc_init(tiger_unit);

  2. 在下面函数中完成 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.4 clk 加入clk_table表中

在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;
  1. clk dts 解析
    以解析soc_clocks device node 为例简单介绍常用device tree API
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>;
};

4.1 of_iomap

在 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)

4.1.1 of_address_to_resource

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中。

4.1.2 __of_address_to_resource

后续解析

4.1.3 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中,方便后续的查找使用。该接口会在后面再详细介绍。

4.2 clock provider DTS第二种写法

每一个可输出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不能使用(或者可理解为不可见);

你可能感兴趣的:(#,ARM,Linux,设备驱动系列介绍,linux,clock,driver,clock,framwork)