4.2.5. 设计说明

4.2.5.1. 源码说明

内核的时钟驱动框架位于linux-5.10/drivers/clk目录下,CMU的底层驱动位于/drivers/clk/zx/目录下。

ZX的目录结构如下图所示:

文件

说明

clk-aic.h

aic公用头文件

clk-aic.c

CMU各个时钟的初始化,注册文件

clk-disp.c

显示模块的时钟文件

clk-fixed-parent-mod.c

只有一个父时钟源的时钟文件

clk-multi-parent-mod.c

具有多个父时钟源的时钟文件

clk-pll.c

PLL时钟文件

4.2.5.2. 模块架构

4.2.5.2.1. clock

按照CCF框架,时钟分为六类:

  • fixed rate clock

  • gate clock

  • divider clock

  • mux clock

  • fixed clock

  • composite clock

时钟树中的每一个divider、gate、mux等都需要定义一个struct clk_hw结构体。CMU模块中有非常多的gate和divider,所以为了代码的简洁性和易用性,CMU的驱动并未严格按照CCF框架编写。CMU驱动模块将时钟分为五种类型:

  • fixed rate clock

  • fixed parent module clock

  • multiple parent module clock

  • display module clock

  • pll clock

fixed rate clock包含OSC24M、RC1M、OSC32K三个时钟,这种时钟频率固定,不能调节频率,不能打开或关闭(即底层ops无enable和disable函数)。

fixed parent module clock实现只有一个父时钟源的时钟驱动,主要是各个外设模块的时钟,该类型时钟可以改变时钟频率,打开或关闭时钟,获取父时钟源参数,但不能设置或改变父时钟源。

multiple parent module clock实现有多个父时钟源的时钟驱动,主要是各种总线时钟,该类型的时钟最为复杂,可以打开或关闭时钟,调节频率,获取或改变父时钟源。

display module clock实现了几个与显示模块相关的时钟驱动,由于显示模块除了自身的模块时钟外,还有一个像素时钟,相应的底层寄存器的设计也不同,所以将显示相关的几个时钟重新设计了底层驱动。

pll clock实现了CMU的pll时钟驱动。

在上述的几种分类中,每中分类都自定义了一个该类型的结构体,基于该结构体实现各种时钟操作。在fixed parent module的结构体中,定义了模块的bus_gate和module_gate,以及该类型时钟的分频系数,相当于综合了CCF框架中的gate和divider。multiple parent module的结构体中定义了gate,mux以及分频系数,相当于综合了CCF框架中的gate,divider和mux。几种类型的时钟支持的API接口如下:

类型

fixed rate clock

fixed parent clock

multi parent clock

disp clock

pll clock

clk_prepare

clk_prepare_enable

clk_unprepare

clk_disable_unprepare

clk_set_rate

clk_get_rate

clk_round_rate

clk_set_parent

clk_get_parent

recalc_rate

4.2.5.2.1.1. 时钟树框图

../../../_images/clock_tree1.png

根据CMU驱动中对时钟的五种分类,对时钟树中各个时钟的归类进行了划分,如上图所示。

4.2.5.2.1.2. fixed rate clock

属于该类型的时钟有:

类型

时钟

fixed rate clock

OSC24M

OSC32K

RC1M

4.2.5.2.1.3. fixed parent clock

类型

时钟

fixed parent clock

CLK_DMA

CLK_CE

CLK_USBD

CLK_USBH0-1

CLK_USB_PHY0-1

CLK_GMAC0-1

CLK_SPI0-1

CLK_SDMMC0-2

CLK_SYSCON

CLK_RTC

CLK_I2S0-1

CLK_ADDA

CLK_DE

CLK_GE

CLK_VE

CLK_WDOG

CLK_SID

CLK_GTC

CLK_GPIO

CLK_UART0-7

CLK_TWI0-3

CLK_CAN0-1

CLK_PWM

CLK_ADCIM

CLK_GPADC

CLK_RTP

CLK_TSEN

CLK_CIR

CLK_RGB

CLK_LVDS

CLK_MIPIDSI

4.2.5.2.1.4. multiple parent clock

属于该类型的时钟有:

类型

时钟

multi parent clock

CLK_CPU

CLK_AHB0

CLK_APB0

CLK_APB1

CLK_AXI0

CLK_OUT0

CLK_OUT1

CLK_OUT2

CLK_OUT3

4.2.5.2.1.5. pll clock

属于该类型的时钟有:

类型

时钟

pll clock

CLK_PLL_INT0

CLK_PLL_INT1

CLK_PLL_FRA0

CLK_PLL_FRA1

CLK_PLL_FRA2

4.2.5.2.1.6. disp clock

属于该类型的时钟有:

类型

时钟

disp clock

CLK_PIX

CLK_SCLK

4.2.5.2.2. reset

CMU模块的reset驱动实现基于内核提供的reset framework。其实现过程是创建并填充内核提供的reset controller设备结构体(struct reset_controller_dev),并调用相应的接口:

  • reset_controller_register

  • reset_controller_unregister

注册或注销。reset controller的结构体如下:

struct reset_controller_dev {
        const struct reset_control_ops *ops;
        struct module *owner;
        struct list_head list;
        struct list_head reset_control_head;
        struct device *dev;
        struct device_node *of_node;
        int of_reset_n_cells;
        int (*of_xlate)(struct reset_controller_dev *rcdev,
                    const struct of_phandle_args *reset_spec);
        unsigned int nr_resets;
};

驱动实现过程主要是对reset_control_ops结构体中的函数指针进行填充,基本上是reset驱动的所有工作量。在CMU模块的reset驱动中,实现了对assert和deassert及status三个函数指针的填充。

4.2.5.3. 关键流程设计

4.2.5.3.1. 初始化流程

4.2.5.3.1.1. clock驱动初始化

通过CLK_OF_DECLARE宏定义,CMU的clock驱动会在__clock_of_table段存放一个struct of_device_id类型的变量。在系统初始化内核时,调用of_clk_init函数,在该函数中调用相应的时钟初始化函数。初始化流程如下:

../../../_images/clock_init.png

4.2.5.3.1.2. reset驱动初始化

通过postcore_initcall宏,将reset驱动存放到.initcall2.init段中。在系统初始化内核时,调用aic_reset_init函数进行reset controller的初始化和注册。

4.2.5.4. 数据结构设计

CMU模块关键结构体定义如下:

4.2.5.4.1. fixed_parent_clk_cfg

struct fixed_parent_clk_cfg {              //fixed parent clock的配置结构体
    u32 id;                            //fixed parent clock的索引值,参考3.2节CLK_xxx
    u16 type;
    u8 fact_mult;
    u8 fact_div;
    const char *name;                  //fixed parent clock的名字
    const char * const *parent_names;    //父时钟的名字
    int num_parents;                   //父时钟个数
    u32 offset_reg;                     //时钟在CMU中的偏移地址
    s8 bus_gate_bit;                    //总线使能位偏移
    s8 mod_gate_bit;                   //模块使能位偏移
    u8 div_bit;                         //分频系数偏移
    u8 div_width;                       //分频系数所占位宽
    struct clk_hw *(*func)(void __iomem *base, const struct fixed_parent_clk_cfg *cfg); //指向初始化和注册fixed parent时钟的函数指针
};

4.2.5.4.2. multi_parent_clk_cfg

struct multi_parent_clk_cfg {                 //multi parent clock的配置结构体
    u32 id;                               //multi parent clock的索引值,参考3.3节CLK_xxx
    const char *name;
    const char * const *parent_names;
    int num_parents;
    u32 offset_reg;
    s32 gate_bit;
    u8 mux_bit;                          //父时钟源选择位的bit偏移
    u8 mux_width;                        //父时钟源选择位所占位宽
    u8 div0_bit;                           //分频系数偏移
    u8 div0_width;                         //分频系数所占位宽
    struct clk_hw *(*func)(void __iomem *base, const struct multi_parent_clk_cfg *cfg); //指向初始化和注册multi parent时钟的函数指针
};

4.2.5.4.3. pll_clk_cfg

struct pll_clk_cfg {                          //pll时钟的配置结构体
    u32 id;                               //pll时钟的索引值,参考3.4节CLK_xxx
    enum aic_pll_type type;                 //pll时钟的类型,是整数分频还是小数分频
    const char *name;
    const char * const *parent_names;
    int num_parents;
    u32 offset_int;                         //整数分频寄存器的偏移
    u32 offset_fra;                         //小数分频寄存器的偏移
    u32 offset_sdm;                       //展频寄存器的偏移
    struct clk_hw *(*func)(void __iomem *base, const struct pll_clk_cfg *cfg); //指向初始化和注册pll时钟的函数指针
};

4.2.5.4.4. disp_clk_cfg

struct disp_clk_cfg {                          //显示模块时钟配置的结构体
    u32 id;                                 //显示模块时钟的索引值,参考3.5节CLK_xxx
    const char *name;
    const char * const *parent_names;
    int num_parents;
    u32 offset_reg;                          //显示模块时钟使能寄存器
    s8 bus_gate_bit;                         //显示模块总线使能位偏移
    s8 mod_gate_bit;                        //显示模块模块使能位偏移
    u32 offset_div_reg;                      //显示模块分频寄存器偏移
    u8 divn_bit;                            //分频系数N偏移
    u8 divn_width;                         //分频系数N所占位宽
    u8 divm_bit;                           //分频系数M偏移
    u8 divm_width;                         //分频系数M所占位宽
    u8 flag_bit;                             //分频系数M标志位
    struct clk_hw *(*func)(void __iomem *base, const struct disp_clk_cfg *cfg); //指向初始化和注册显示模块时钟的函数指针
};

4.2.5.5. 接口设计

4.2.5.5.1. aic_clk_hw_fixed_parent_module

函数原型

struct clk_hw *aic_clk_hw_fixed_parent(void __iomem *base, const struct fixed_parent_clk_cfg *cfg)

功能说明

初始化fixed parent clock,并对时钟进行注册

参数定义

base:CMU寄存器的基地址
cfg:指向配置参数的指针

返回值

返回struct clk_hw*类型的指针

注意事项

4.2.5.5.2. aic_clk_hw_multi_parent_module

函数原型

struct clk_hw *aic_clk_hw_multi_parent(void __iomem *base, const struct multi_parent_clk_cfg *cfg)

功能说明

初始化multi parent clock,并对时钟进行注册

参数定义

base:CMU寄存器的基地址
cfg:指向配置参数的指针

返回值

返回struct clk_hw*类型的指针

注意事项

4.2.5.5.3. aic_clk_hw_pll

函数原型

struct clk_hw *aic_clk_hw_pll(void __iomem *base, const struct pll_clk_cfg *cfg)

功能说明

初始化pll clock,并对时钟进行注册

参数定义

base:CMU寄存器的基地址
cfg:指向配置参数的指针

返回值

返回struct clk_hw*类型的指针

注意事项

4.2.5.5.4. aic_clk_hw_disp

函数原型

struct clk_hw *aic_clk_hw_disp(void __iomem *base, const struct disp_clk_cfg *cfg)

功能说明

初始化disp clock,并对时钟进行注册

参数定义

base:CMU寄存器的基地址
cfg:指向配置参数的指针

返回值

返回struct clk_hw*类型的指针

注意事项