4.2.5. 设计说明¶
4.2.5.1. 源码说明¶
内核的时钟驱动框架位于linux-5.10/drivers/clk目录下,CMU的底层驱动位于/drivers/clk/artinchip/目录下。
Artinchip的目录结构如下图所示:
| 文件 | 说明 | 
|---|---|
| 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.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_I2C0-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.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函数,在该函数中调用相应的时钟初始化函数。初始化流程如下:
 
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*类型的指针 | 
| 注意事项 | 
