时钟
I.MX6U 时钟系统详解
I.MX6U 的系统主频为528MHz,有些型号可以跑到 696MHz,但是默认情况下内部 boot rom 会将 I.MX6U 的主频设置为396MHz。
《I.MX6ULL/I.MX6UL 参考手册》的第 10 章和第18 章有详细的讲解。
系统时钟来源
系统时钟来源于两部分: 32.768KHz 晶振是 I.MX6U 的 RTC 时钟源, 24MHz 晶振是 I.MX6U 内核和其它外设的时钟源。
7 路 PLL 时钟源
①、 ARM_PLL(PLL1),此路 PLL 是供 ARM 内核使用的,此 PLL 通过编程的方式最高可倍频到 1.3GHz。
②、 528_PLL(PLL2),此路 PLL 也叫做 System_PLL,此路 PLL 是固定的 22 倍频,不可编程修改。 此路 PLL 时钟=24MHz * 22 = 528MHz,分出了 4 路 PFD和528_PLL。 通常用于I.MX6U 内部系统总线的时钟源,比如内处理逻辑单元、 DDR 接口、 NAND/NOR 接口等等。
③、 USB1_PLL(PLL3),此路 PLL 主要用于 USB1PHY,此 PLL 也有四路 PFD和USB1_PLL,USB1_PLL 是固定的 20 倍频,因此USB1_PLL=24MHz *20=480MHz。
④、 USB2_PLL(PLL7),此路 PLL 主要用于 USB2PHY,频率同PLL3。
⑤、 ENET_PLL(PLL6),此路 PLL 固定为 20+5/6 倍频,因此 ENET_PLL=24MHz * (20+5/6)= 500MHz,主要用于生成网络所需的时钟,可以在此 PLL 的基础上生成 25/50/100/125MHz的网络时钟。
⑥、 VIDEO_PLL(PLL5),PLL 的输出范围在 650MHz~1300MHz。此路 PLL 用于显示相关的外设,比如 LCD,此路 PLL 的倍频可以调整, 此路 PLL 在最终输出的时候还可以进行分频,可选 1/2/4/8/16 分频。
⑦、 AUDIO_PLL(PLL4) ,PLL的输出范围同样也是650MHz~1300MHz,此路 PLL 用于音频相关的外设,此路 PLL 的倍频可以调整,此路 PLL 在最终输出的时候也可以进行分频,可选1/2/4 分频。
总结:时钟系统就是晶振脉冲进入系统通过不同路的倍频和分频提供给外设。
时钟树简介
这些外设究竟是如何选择 PLL 或者 PFD 的?这个就要借助时钟树了。参考《IMX6ULL 参考手册》18.3 小节给出了 I.MX6U详细的时钟树图。
时钟树一共有三部分:
CLOCK_SWITCHER包含7 路 PLL 和8 路 PFD。
SYSTEM CLOCKS包括芯片外设
CLOCK ROOT GENERATOR 给左边的 CLOCK_SWITCHER和右边的 SYSTEM CLOCKS进行牵线搭桥。
具体操作肯定是设置相应的寄存器啦!!!,使用寄存器控制选择器和分频器。
内核时钟设置
①、内核时钟源来自于 PLL1,假如此时 PLL1 为 996MHz,可以通过寄存器 CCM_ANALOG_PLL_ARMn 来设置。
②、通过寄存器 CCM_CACRR 的 ARM_PODF 位对 PLL1 进行分频,可选择 1/2/4/8 分频。
③、大家不要被此处的 2 分频给骗了,此处没有进行 2 分频,啥都没有。
寄存器 CCM_CACRR,分频
寄存器 CCM_CACRR 只有 ARM_PODF 位,可以设置为 0 ~ 7,分别对应 1~ 8 分频,如下图所示:
寄存器 CCM_ANALOG_PLL_ARMn,倍频
寄存器 CCM_ANALOG_PLL_ARMn,如下图。
ENABLE: 时钟输出使能位,此位设置为 1 使能 PLL1 输出,如果设置为 0 的话就关闭 PLL1输出。
DIV_SELECT: 此位设置 PLL1 的输出频率,可设置范围为: 54~108,PLL1 CLK = Fin *div_seclec/2.0, Fin=24MHz。如果 PLL1 要输出 1056MHz 的话, div_select 就要设置为 88。
PLL1 时钟频率的修改
这里PLL1 时钟频率的修改还需要暂时让其他时钟代替一下。
用到了3个选择器进行切换,如图3个黄色区域,都位于CCM_CCSR。
到这里,修改 I.MX6U 主频的步骤就很清晰了,修改步骤如下:
①、 设置寄存器 CCSR 的 STEP_SEL 位,设置 step_clk 的时钟源为 24M 的晶振。
②、设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,设置 pll1_sw_clk 的时钟源为step_clk=24MHz,通过这一步我们就将 I.MX6U 的主频先设置为 24MHz,直接来自于外部的24M 晶振。
③、设置寄存器 CCM_ANALOG_PLL_ARMn,将pll1_main_clk(PLL1)设置为 1056MHz。
④、设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,重新将 pll1_sw_clk 的时钟源切换回pll1_main_clk,切换回来以后的 pll1_sw_clk 就等于 1056MHz。
⑤、最后设置寄存器 CCM_CACRR 的 ARM_PODF 为 2 分频, I.MX6U 的内核主频就为1056/2=528MHz。
还是设置寄存器,查询手册知道地址和需要配置的值即可。
PFD 时钟设置
PLL2、 PLL3 和 PLL7 固定为 528MHz、 480MHz 和 480MHz, PLL4~PLL6 都是针对特殊外设的,用到的时候再设置。
本节先来设置PLL2 和 PLL3 的各自 4 路 PFD。
PLL2 的 4 路 PFD
PLL2 的 4 路 PFD 频率,用到寄存器是 CCM_ANALOG_PFD_528n。
PFD0~PFD3共4组,每组 8 个 bit。
以PFD0为例:
PFD0_FRAC: PLL2_PFD0 的分频数, PLL2_PFD0 的计算公式为 528*18/PFD0_FRAC,此为 可 设 置 的 范 围 为 12~35 。
PFD0_STABLE: 此位为只读位,可以通过读取此位判断 PLL2_PFD0 是否稳定。
PFD0_CLKGATE: PLL2_PFD0 输出使能位,为 1 的时候关闭 PLL2_PFD0 的输出,为 0 的时候使能输出。
PLL3 的 4 路 PFD
用 到 的 寄 存 器 是CCM_ANALOG_PFD_480n。
配置同PLL2的4个PFD。
AHB、 IPG 和 PERCLK 根时钟设置
7 路 PLL 和 8 路 PFD 设置完成以后最后还需要设置AHB_CLK_ROOT 、 IPG_CLK_ROOT和 PERCLK_CLK_ROOT的时钟。
I.MX6U 外设根时钟可设置范围如图:
需要参考的时钟图如下图所示:
需要参考的寄存器如下图所示:
1、CCM_CBCDR 寄存器
PERIPH_CLK2_PODF: periph2 时钟分频,可设置 0~ 7,分别对应 1~ 8 分频。
PERIPH2_CLK_SEL:选择 peripheral2 的主时钟,如果为 0 的话选择 PLL2,如果为 1 的话选择 periph2_clk2_clk。修改此位会引起一次与 MMDC 的握手,所以修改完成以后要等待握手完成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。
PERIPH_CLK_SEL: peripheral 主时钟选择,如果为 0 的话选择 PLL2,如果为 1 的话选择 periph_clk2_clock。修改此位会引起一次与 MMDC 的握手,所以修改完成以后要等待握手完
成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。
AXI_PODF: axi 时钟分频,可设置 0~ 7,分别对应 1~ 8 分频。
AHB_PODF: ahb 时钟分频,可设置 0~ 7,分别对应 1~ 8 分频。修改此位会引起一次与MMDC 的握手,所以修改完成以后要等待握手完成,握手完成信号由寄存器 CCM_CDHIPR 中指定位表示。
IPG_PODF: ipg 时钟分频,可设置 0~3,分别对应 1~4 分频。
AXI_ALT_CLK_SEL: axi_alt 时钟选择,为 0 的话选择PLL2_PFD2,如果为 1 的话选择PLL3_PFD1。
AXI_CLK_SEL: axi 时钟源选择,为 0 的话选择 periph_clk,为 1 的话选择 axi_alt 时钟。
FABRIC_MMDC_PODF: fabric/mmdc 时钟分频设置,可设置 0~ 7,分别对应 1~8 分频。
PERIPH2_CLK2_PODF: periph2_clk2 的时钟分频,可设置 0 ~ 7,分别对应 1 ~ 8 分频。
2、CCM_CBCMR寄存器
LCDIF1_PODF: lcdif1 的时钟分频,可设置 0~ 7,分别对应 1~ 8 分频。
PRE_PERIPH2_CLK_SEL: pre_periph2 时钟源选择, 00 选择 PLL2, 01 选择 PLL2_PFD2,10 选择 PLL2_PFD0, 11 选择 PLL4。PERIPH2_CLK2_SEL: periph2_clk2 时钟源选择为 0 的时候选择 pll3_sw_clk,为 1 的时候选择 OSC。
PRE_PERIPH_CLK_SEL: pre_periph 时钟源选择, 00 选择 PLL2, 01 选择 PLL2_PFD2, 10 选择 PLL2_PFD0, 11 选择 PLL2_PFD2/2。
PERIPH_CLK2_SEL: peripheral_clk2 时钟源选择, 00 选择 pll3_sw_clk, 01 选择 osc_clk,10 选择 pll2_bypass_clk。
3、CCM_CSCMR1寄存器
此寄存器主要用于外设时钟源的选择,比如 QSPI1、 ACLK、 GPMI、 BCH 等外设,我们重点看一下下面
PERCLK_CK_SEL: perclk 时钟源选择,为 0 的话选择 ipg clk,为 1 的话选择 osc clk。
PERCLK_PODF: perclk 的时钟分频,可设置 0~7,分别对应 1 ~ 8 分频。
在修改 arm_podf 和 ahb_podf 的时候需要先关闭其时钟输出,等修改完成以后再打开,否则的话可能会出现在修改完成以后没有时钟输出的问题。需要修改寄存器CCM_CBCDR 的 AHB_PODF 位来设置 AHB_ROOT_CLK 的时钟,所以在修改之前必须先关闭AHB_ROOT_CLK 的输出。但是左老师没有找到相应的寄存器,因此目前没法关闭,那也就没法设置 AHB_PODF 了。不过 AHB_PODF 内部 boot rom 设置为了 3 分频,如果 pre_periph_clk 的时钟源选择 PLL2_PFD2 的话, AHB_ROOT_CLK 也是 396MHz/3=132MHz。
实验程序编写
bsp_clk.c
1 #include "bsp_clk.h"
2
3/***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5 文件名 : bsp_clk.c
6 作者 : 左忠凯
7 版本 : V1.0
8 描述 : 系统时钟驱动。
9 其他 : 无
10 论坛 : www.openedv.com
11 日志 : 初版 V1.0 2019/1/3 左忠凯创建
12
13 V2.0 2019/1/3 左忠凯修改
14 添加了函数 imx6u_clkinit(),完成 I.MX6U 的系统时钟初始化
15 ***************************************************************/
16
17 /*
18 * @description : 使能 I.MX6U 所有外设时钟
19 * @param : 无
20 * @return : 无
21 */
22 void clk_enable(void)
23 {
24 CCM->CCGR0 = 0XFFFFFFFF;
25 CCM->CCGR1 = 0XFFFFFFFF;
26 CCM->CCGR2 = 0XFFFFFFFF;
27 CCM->CCGR3 = 0XFFFFFFFF;
28 CCM->CCGR4 = 0XFFFFFFFF;
29 CCM->CCGR5 = 0XFFFFFFFF;
30 CCM->CCGR6 = 0XFFFFFFFF;
31 }
32
33 /*
34 * @description : 初始化系统时钟 528Mhz,并且设置 PLL2 和 PLL3 各个
35 PFD 时钟,所有的时钟频率均按照 I.MX6U 官方手册推荐的值.
36 * @param : 无
37 * @return : 无
38 */
39 void imx6u_clkinit(void)
40 {
41 unsigned int reg = 0;
42 /* 1、设置 ARM 内核时钟为 528MHz */
43 /* 1.1、判断当使用哪个时钟源启动的,正常情况下是由 pll1_sw_clk 驱动的,而
44 * pll1_sw_clk 有两个来源: pll1_main_clk 和 step_clk, 如果要
45 * 让 I.MX6ULL 跑到 528M, 那必须选择 pll1_main_clk 作为 pll1 的时钟
46 * 源。 如果我们要修改 pll1_main_clk 时钟的话就必须先将 pll1_sw_clk 从
47 * pll1_main_clk 切换到 step_clk,当修改完以后再将 pll1_sw_clk 切换
48 * 回 pll1_main_cl, step_clk 等于 24MHz。
49 */
50
51 if((((CCM->CCSR) >> 2) & 0x1 ) == 0) /* pll1_main_clk */
52 {
53 CCM->CCSR &= ~(1 << 8); /* 配置 step_clk 时钟源为 24MHz OSC */
54 CCM->CCSR |= (1 << 2); /* 配置 pll1_sw_clk 时钟源为 step_clk */
55 }
56
57 /* 1.2、设置 pll1_main_clk 为 1056MHz,也就是 528*2=1056MHZ,
58 * 因为 pll1_sw_clk 进 ARM 内核的时候会被二分频!
59 * 配置 CCM_ANLOG->PLL_ARM 寄存器
60 * bit13: 1 使能时钟输出
61 * bit[6:0]: 88, 由公式: Fout = Fin * div_select / 2.0,
62 * 1056=24*div_select/2.0, 得出: div_select=88。
63 */
64 CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F);
65 CCM->CCSR &= ~(1 << 2);/* 将 pll_sw_clk 时钟切换回 pll1_main_clk */
66 CCM->CACRR = 1; /* ARM 内核时钟为 pll1_sw_clk/2=1056/2=528Mhz */
67
68 /* 2、设置 PLL2(SYS PLL)各个 PFD */
69 reg = CCM_ANALOG->PFD_528;
70 reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
71 reg |= 32<<24; /* PLL2_PFD3=528*18/32=297Mhz */
72 reg |= 24<<16; /* PLL2_PFD2=528*18/24=396Mhz */
73 reg |= 16<<8; /* PLL2_PFD1=528*18/16=594Mhz */
74 reg |= 27<<0; /* PLL2_PFD0=528*18/27=352Mhz */
75 CCM_ANALOG->PFD_528=reg; /* 设置 PLL2_PFD0~3 */
76
77 /* 3、设置 PLL3(USB1)各个 PFD */
78 reg = 0; /* 清零 */
79 reg = CCM_ANALOG->PFD_480;
80 reg &= ~(0X3F3F3F3F); /* 清除原来的设置 */
81 reg |= 19<<24; /* PLL3_PFD3=480*18/19=454.74Mhz */
82 reg |= 17<<16; /* PLL3_PFD2=480*18/17=508.24Mhz */
83 reg |= 16<<8; /* PLL3_PFD1=480*18/16=540Mhz */
84 reg |= 12<<0; /* PLL3_PFD0=480*18/12=720Mhz */
85 CCM_ANALOG->PFD_480=reg; /* 设置 PLL3_PFD0~3 */
86
87 /* 4、设置 AHB 时钟 最小 6Mhz, 最大 132Mhz */
88 CCM->CBCMR &= ~(3 << 18); /* 清除设置*/
89 CCM->CBCMR |= (1 << 18); /* pre_periph_clk=PLL2_PFD2=396MHz */
90 CCM->CBCDR &= ~(1 << 25); /* periph_clk=pre_periph_clk=396MHz */
91 while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 */
92
93 /* 修改 AHB_PODF 位的时候需要先禁止 AHB_CLK_ROOT 的输出,但是
94 * 我没有找到关闭 AHB_CLK_ROOT 输出的的寄存器,所以就没法设置。
95 * 下面设置 AHB_PODF 的代码仅供学习参考不能直接拿来使用!!
96 * 内部 boot rom 将 AHB_PODF 设置为了 3 分频,即使我们不设置 AHB_PODF,
97 * AHB_ROOT_CLK 也依旧等于 396/3=132Mhz。
98 */
99 #if 0
100 /* 要先关闭 AHB_ROOT_CLK 输出,否则时钟设置会出错 */
101 CCM->CBCDR &= ~(7 << 10);/* CBCDR 的 AHB_PODF 清零 */
102 CCM->CBCDR |= 2 << 10; /* AHB_PODF 3 分频, AHB_CLK_ROOT=132MHz */
103 while(CCM->CDHIPR & (1 << 1));/* 等待握手完成 */
104 #endif
105
106 /* 5、设置 IPG_CLK_ROOT 最小 3Mhz,最大 66Mhz */
107 CCM->CBCDR &= ~(3 << 8); /* CBCDR 的 IPG_PODF 清零 */
108 CCM->CBCDR |= 1 << 8; /* IPG_PODF 2 分频, IPG_CLK_ROOT=66MHz */
109
110 /* 6、设置 PERCLK_CLK_ROOT 时钟 */
111 CCM->CSCMR1 &= ~(1 << 6); /* PERCLK_CLK_ROOT 时钟源为 IPG */
112 CCM->CSCMR1 &= ~(7 << 0); /* PERCLK_PODF 位清零,即 1 分频 */
113 }
imx6u_clkinit(); /* 初始化系统时钟 */
clk_enable(); /* 使能所有的时钟 */