一、时钟域的概念
1、由于时钟体系复杂,内部外设模块太多,因此把整个内部的时钟划分为3大类。
- MSYS:CPU(Cortex-A8)、DRAM控制器(DMC0和DMC1),IRAM&IROM的时钟来源。
- ARMCLK :给CPU内核工作的时钟,也就是我们经常说的主频;
- HCLK_MSYS:MSYS域的高频时钟,给DMC0和DMC1使用的;
- PCLK_MSYS:MSYS域的低频时钟;
- HCLK_IMEM:给iROM和iRAM使用的时钟。
- DSYS:都是和视频显示,编解码等有关的模块。
- HCLK_DSYS:DSYS域的高频时钟;
- PCLK_DSYS:DSYS域的低频时钟。
- PSYS:和内部各种外设时钟有关的。串口、SD卡接口、USB、GPIO、IIC等。
- HCLK_PSYS:PSYS域的高频时钟;
- PCLK_PSYS:PSYS域的低频时钟;
- SCLK_ONENAND:
因为x210的这些模块的工作的时钟速率差异太大,所以有必要把高速的模块放在一起,相对低速的放在一起。
2、时钟来源
S5PV210外部有4个晶振接口,设计板子的时候,可以根据需要来决定接哪几个晶振。接了晶振之后,给开发板上电,相应的模块就能产生振荡,产生原始时钟。原始时钟再经过一系列的筛选开关进入相应的PLL电路,生成倍频后的高频时钟。高频时钟经过分频器,达到芯片内部的各个模块上。
3、芯片内部的四个PLL锁相环,倍频用的
- APPL:Cortex-A8和MSYS域时钟的来源。
- MPLL&EPLL:DSYS和PSYS时钟来源和一些其他的外设模块的时钟。
- VPLL:Video相关的时钟。
总结:210内部的各个外设都是接在内部AMBA总线上的,AMBA总线有一条高频分支叫AHB,有一条低频总线叫APB,上面的各个模块都是挂在总线上工作的,所以总线的时钟是多少,挂在总线下面的模块时钟就是多少。
4、三星推荐的芯片工作的典型值
- freq(ARMCLK) = 1000 MHz
- freq(HCLK_MSYS) = 200 MHz
- freq(HCLK_IMEM) = 100 MHz
- freq(PCLK_MSYS) = 100 MHz
- freq(HCLK_DSYS) = 166 MHz
- freq(PCLK_DSYS) = 83 MHz
- freq(HCLK_PSYS) = 133 MHz
- freq(PCLK_PSYS) = 66 MHz
- freq(SCLK_ONENAND) = 133 MHz, 166 MHz
二、S5PV210时钟分析
1、这里面涉及到两个关键的器件
- MUX:多路选择器,从多个输入中选择一个输入,作为输出。
- DIV:分频器,将输入的时钟,经过一定的分频,输出到下一个模块。
MUX选择路线,DIV用于设置频率大小。
2、时钟配置所涉及到的关键寄存器
(1)xPLL_LOCK寄存器:
这些寄存器用来控制PLL的锁定周期的,就是PLL锁相环经过多长的时间,可以输出稳定的时钟频率。
(2)xPLL_CONn寄存器
例如APLL_CON0寄存器的各位详解:
- [31]:用于控制PLL的开关。
- [29]:只读寄存器,可以从该位,判断我们当前的APLL是否锁定了。
- MDIV、PDIV和SDIV用于计算APLL倍频后的时钟频率,计算公式如下:
FOUT = MDIV X FIN / (PDIV × 2S^(DIV-1))
例如我们系统推荐APLL的输出为1000MHz的时钟频率,输入为24MHz的晶振,我们设置P、M、S的数值为:
- MDIV = 125
- PDIV = 3
- SDIV = 1
FOUT = 125 * 24/(3*2^0) = 125*8=1000
(3)CLK_SRCn(0-6)
设置始终来源,这个寄存器就是用来控制时钟框图中的对应的MUX器件,选择不同时钟来源。
例如这里CLK_SRC0寄存器:
[0]控制这时钟框图中MUXAPLL这个选择器选择什么作为输出。
(4)CLK_SRC_MASKn寄存器
这个寄存器,类似于在MUX选择器,选择输出之后,加了一个开关,只有开关打开,时钟才能输出到下一个对应的模块中。
(5)CLK_DIVn寄存器
这个寄存器,用来设置分频系数的。
(6)CLK_GATE_X寄存器
这个寄存器的作用类似于CLK_SRC_MASKn寄存器的作用,不过放置的位置是不同的。
(7)CLK_DIV_STAn和CLK_MUX_STAn
查看DIV和MUX的状态是否已经完成还是在进行中。
总结:这么多寄存器,其实还有很多寄存器,但是最重要的有3个寄存器
- CON控制寄存器,控制PLL的倍频。
- SRC寄存器,选择走那条路。就是时钟的来源设置。
- DIV寄存器,分频的设置。
三、代码实践
1、时钟设置的步骤
- 设置各种时钟开关,暂时不使用PLL;
- 设置锁定时间,设置为最大值;
- 设置分频
- 设置PLL,主要是设置PLL的倍频系统,决定有输入端的24M的原始频率可以得到多大的输出频率,我们按默认的设置值,设置为1G;
- 打开PLL。
2、代码分析
Step1:r0存储的是时钟寄存器的基地址,采用基地址+偏移地址的方式,寻址不同的寄存器。
// 1 设置各种时钟开关,暂时不使用PLL
ldr r1, =0x0
// 芯片手册P378 寄存器CLK_SRC:Select clock source 0 (Main)
str r1, [r0, #CLK_SRC0_OFFSET]
将该寄存器设置为0,因为一开始,我们的时钟来源,以及时钟的分频系数,都没有初始化,所以一开始不能使用PLL,使用的频率是外部晶振的实际频率,等我们初始化好了相关寄存器,才打开PLL,然后系统才能按照我们设定的频率工作。
Step2:
// 2 设置锁定时间,使用默认值即可
// 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间
ldr r1, =0x0000FFFF
str r1, [r0, #APLL_LOCK_OFFSET]
str r1, [r0, #MPLL_LOCK_OFFSET]
Step3:
// 3 设置分频
// 清bit[0~31]
ldr r1, [r0, #CLK_DIV0_OFFSET]
ldr r2, =CLK_DIV0_MASK
bic r1, r1, r2 //清零需要设置的那几位
ldr r2, =0x14131440
orr r1, r1, r2
str r1, [r0, #CLK_DIV0_OFFSET]
对应的结果就是前面时钟框图中,写的数字。就是最后设置的个分频器的结果。
Step4:
// 4 设置PLL
// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHz
ldr r1, =APLL_VAL
str r1, [r0, #APLL_CON0_OFFSET]
// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHz
ldr r1, =MPLL_VAL
str r1, [r0, #MPLL_CON_OFFSET]
设置MPLL和APLL的倍频系数。
Step5:
// 5 设置各种时钟开关,使用PLL
ldr r1, [r0, #CLK_SRC0_OFFSET]
ldr r2, =0x10001111
orr r1, r1, r2
str r1, [r0, #CLK_SRC0_OFFSET]
经过前面几步,我们已经设置好的分频系数和时钟来源的选择,打开了PLL,所以这里选择PLL的输出时钟,作为系统相关模块的时钟频率。
完整的汇编代码:
// 时钟控制器基地址
#define ELFIN_CLOCK_POWER_BASE 0xE0100000
// 时钟相关的寄存器相对时钟控制器基地址的偏移值
#define APLL_LOCK_OFFSET 0x00
#define MPLL_LOCK_OFFSET 0x08
#define APLL_CON0_OFFSET 0x100
#define APLL_CON1_OFFSET 0x104
#define MPLL_CON_OFFSET 0x108
#define CLK_SRC0_OFFSET 0x200
#define CLK_SRC1_OFFSET 0x204
#define CLK_SRC2_OFFSET 0x208
#define CLK_SRC3_OFFSET 0x20c
#define CLK_SRC4_OFFSET 0x210
#define CLK_SRC5_OFFSET 0x214
#define CLK_SRC6_OFFSET 0x218
#define CLK_SRC_MASK0_OFFSET 0x280
#define CLK_SRC_MASK1_OFFSET 0x284
#define CLK_DIV0_OFFSET 0x300
#define CLK_DIV1_OFFSET 0x304
#define CLK_DIV2_OFFSET 0x308
#define CLK_DIV3_OFFSET 0x30c
#define CLK_DIV4_OFFSET 0x310
#define CLK_DIV5_OFFSET 0x314
#define CLK_DIV6_OFFSET 0x318
#define CLK_DIV7_OFFSET 0x31c
#define CLK_DIV0_MASK 0x7fffffff
// 这些M、P、S的配置值都是查数据手册中典型时钟配置值的推荐配置得来的。
// 这些配置值是三星推荐的,因此工作最稳定。如果是自己随便瞎拼凑出来的那就要
// 经过严格测试,才能保证一定对。
#define APLL_MDIV 0x7d // 125
#define APLL_PDIV 0x3
#define APLL_SDIV 0x1
#define MPLL_MDIV 0x29b // 667
#define MPLL_PDIV 0xc
#define MPLL_SDIV 0x1
#define set_pll(mdiv, pdiv, sdiv) (1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VAL set_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VAL set_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)
.global clock_init
clock_init:
ldr r0, =ELFIN_CLOCK_POWER_BASE
// 1 设置各种时钟开关,暂时不使用PLL
ldr r1, =0x0
// 芯片手册P378 寄存器CLK_SRC:Select clock source 0 (Main)
str r1, [r0, #CLK_SRC0_OFFSET]
// 2 设置锁定时间,使用默认值即可
// 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间
ldr r1, =0x0000FFFF
str r1, [r0, #APLL_LOCK_OFFSET]
str r1, [r0, #MPLL_LOCK_OFFSET]
// 3 设置分频
// 清bit[0~31]
ldr r1, [r0, #CLK_DIV0_OFFSET]
ldr r2, =CLK_DIV0_MASK
bic r1, r1, r2 //清零需要设置的那几位
ldr r2, =0x14131440
orr r1, r1, r2
str r1, [r0, #CLK_DIV0_OFFSET]
// 4 设置PLL
// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHz
ldr r1, =APLL_VAL
str r1, [r0, #APLL_CON0_OFFSET]
// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHz
ldr r1, =MPLL_VAL
str r1, [r0, #MPLL_CON_OFFSET]
// 5 设置各种时钟开关,使用PLL
ldr r1, [r0, #CLK_SRC0_OFFSET]
ldr r2, =0x10001111
orr r1, r1, r2
str r1, [r0, #CLK_SRC0_OFFSET]
mov pc, lr //函数返回
- 整个程序的源码下载(包括启动文件和Makefile):https://download.csdn.net/my
转载:https://blog.csdn.net/qq_35480173/article/details/101697465