• 【Linux Clock Framework】


    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; 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 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,
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 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";
    };
    
    • 1
    • 2
    • 3
    • 4

    该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);
    
    • 1
    • 2

    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
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    成员解释:
    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; 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    因此,对于底层 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);
    
    • 1
    • 2
    • 3
    • 4

    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
    • 2
    • 3
    • 4
    例-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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    例-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";
                  };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 定义好需要注册的 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},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 调用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
    • 2
    • 3

    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
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    由于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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可通过下面接口注册这类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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    另外,这一类接口和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
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 调用tiger 封装的函数完成fixed clk的注册
    tiger_register_fixed_factor_clks(struct tiger_clk_unit *unit,...
    
    • 1

    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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可使用下面接口注册:

    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);
    
    • 1
    • 2
    • 3
    • 4

    需要提供的参数包括:
    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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},
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 调用tiger封装的struct clk *clk_register_gate 注册AP tiger_register_general_gate_clk进行注册,封装主要是为了方便注册,比如将统一类型的clk注册信息填入一个数组中,让后直以数组为参数进行注册,这样减少了代码量。
    #define MPMU_WDTPCR	0x200  //watchdog clk 控制寄存器的偏移地址
    
    • 1
    //注册 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);
    
    • 1
    • 2
    • 3
    • 4

    在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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可通过下面两个接口注册

    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);
    
    • 1
    • 2
    • 3
    • 4

    该接口用于注册分频比规则的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);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    该接口用于注册分频比不规则的 clock,和上面接口比较,差别在于divider 值和寄存器值得对应关系由一个table决定,该table的原型为:

    struct clk_div_table { 
        unsigned int    val; 
        unsigned int    div; 
    };
    
    • 1
    • 2
    • 3
    • 4

    其中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
    • 2
    • 3

    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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可通过下面两个接口注册:

    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);
    
    • 1
    • 2
    • 3
    • 4

    该接口可注册 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);
    
    • 1
    • 2
    • 3
    • 4

    该接口通过一个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
    • 2
    • 3
    • 4
    • 5

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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
    
    • 1

    2.1.3 struct clk 分配

    step_4: 分配一个 struct clk 结构并挂在hw->core->clks上, 然后返回clk指针,

    hw->clk = __clk_create_clk(hw, NULL, NULL);-------->4
    
    • 1

    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    调用内部接口 __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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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]);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    获取当前的 parent clock,并将其保存在parent指针中

    core->parent = __clk_init_parent(core);
    
    • 1

    根据该 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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    此处应该是设置精度,但是从我们的底层驱动看,这个都是设置为默认值

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    设置相位,默认为0

    if (core->ops->get_phase)
    	core->phase = core->ops->get_phase(core->hw);
    else
    	core->phase = 0;
    
    • 1
    • 2
    • 3
    • 4

    计算 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    有些情况下,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;
    			}
    	 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果clock ops提供了init接口,执行之 ,如无特殊硬件层面的限制,不建议实现此init函数

    	if (core->ops->init)
    		core->ops->init(core->hw);
    	kref_init(&core->ref);
    }
    
    • 1
    • 2
    • 3
    • 4

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果提供了 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 并返回
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    例-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";
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    例-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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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);
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在最新的 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>; 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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 {
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当具体的设备(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 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    下面例子使用"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>;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    该宏定义最终解析成下面内容:

    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  //匹配成功后会调用该函数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注册驱动初始化函数主要是进行相关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;         
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ”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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在初始化阶段只要获取 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设备节点,
       ....
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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)
    
    • 1
    • 2
    • 3

    该表中的索引值定义位于”include/dt-bindings/clock/tiger-qucom.h“,其他设备在在dts文件中会指明使用的索引值。
    定义watch dog索引值:

    #define QUCOM_CLK_WDT                                  281
    
    • 1

    dts中使用

    watchdog: watchdog@d4080000 {
              compatible = "tiger,soc-wdt";
              clocks = <&soc_clocks QUCOM_CLK_WDT>;
              reg = <0xd4080000 0xff>,
                     <0xd4050000 0x1024>;
              status = "disabled";
     };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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中
    
    • 1
    • 2
    • 3

    有些 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},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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
    • 2
    • 3
    • 4
    • 5
    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>;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.1 of_iomap

    在 qucom_clk_init 函数中首先会调用of_iomap获取mpmu的基地址:

    tiger_unit->mpmu_base = of_iomap(np, 0);  
    
    • 1

    参数 struct device_node np 即为抽象的clk虚拟设备 (dts中soc_clocks: clocks节点)。如下为 of_iomap 函数的

    of_iomap定义:

    void __iomem *of_iomap(struct device_node *np, int index)
    
    • 1

    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)
    
    • 1
    • 2

    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";
          };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

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

  • 相关阅读:
    视频剪辑srt字幕的添加步骤,三分钟学会这个方法
    这应该是Linux用户与用户组最详细的知识了吧!
    汇川伺服【选型目录】
    使用vite+npm封装组件库并发布到npm仓库
    SpringBoot 学习(七)Swagger
    【编程题】【Scratch四级】2019.12 随机选T恤
    【Redis】Redis最佳实践:键值设计
    JDK动态代理Demo
    矩阵分解算法
    基于springboot的二次元商品销售网站的设计与开发
  • 原文地址:https://blog.csdn.net/sinat_32960911/article/details/133700646