S5PV210之时钟系统

阅读更多

本文使用的开发板是九鼎创展的X210 iNand版本。

 

一、S5PV210的时钟系统简介

 

1、时钟域:MSYS、DSYS、PSYS

 

S5PV210之时钟系统_第1张图片

 

因为S5PV210的时钟体系比较复杂,内部外设模块太多,因此把整个内部的时钟划分为三大块,叫做三个时钟域,分别是MSYS、DSYS、PSYS。

因为S5PV210内部的这些模块彼此工作的时钟速率差异太大,因此把高速的放在一起,相对低速的放在一起。

 

(1)MSYS:CPU(Cortex-A8内核)、DRAM控制器(DMC0和DMC1)、IRAM & IROM等

(2)DSYS:和视频显示、编解码等有关的模块

(3)PSYS:和内部的各种外设时钟有关,例如:串口、SD接口、I2C、AC97、USB等

 

2、时钟来源:晶振 + 时钟发生器 + PLL + 分频电路

 

S5PV210外部有四个晶振接口,设计板子硬件时可以根据需要来决定在哪里接晶振。接了晶振之后,上电相应的模块就能产生振荡,产生原始时钟。原始时钟再经过一系列的筛选开关进入相应的PLL电路生成倍频后的高频时钟。高频时钟再经过分频到达芯片内部的各个模块上。

 

有些模块(串口)内部还有进一步的分频器进行再次分频使用。

 

3、PLL:APLL、MPLL、EPLL、VPLL

 

S5PV210之时钟系统_第2张图片

 

(1)APLL:MSYS域

(2)MPLL:DSYS域

(3)EPLL:PSYS域

(4)VPLL:视频相关模块 

 

4、时钟域和时钟的关系

 

S5PV210之时钟系统_第3张图片

 

(1)MSYS域

       ARMCLK:给CPU内核工作的时钟,也就是所谓的主频

HCLK_MSYS:MSYS域的高频时钟,给DMC0和DMC1使用

PCLK_MSYS:MSYS域的低频时钟

HCLK_IMEM:给IRAM和IROM(合称IMEM)使用

 

(2)DSYS域

HCLK_DSYS:DSYS域的高频时钟

PCLK_DSYS:DSYS域的低频时钟

 

(3)PSYS域

HCLK_PSYS:PSYS域的高频时钟

PCLK_PSYS:PSYS域的低频时钟

SCLK_ONENAND

 

S5PV210内部的各个外设都是接在内部AMBA总线上面的,AMBA总线有一条高频分支叫AHB,有一条低频分支叫APB。上面的各个域都有各自对应的HCLK_XXXX和PCLK_XXXX,其中HCLK_XXXX就是XXXX域中AHB总线的工作频率,PCLK_XXXX就是XXXX域中APB总线的工作频率。

 

SoC内部的各个外设其实是挂在总线上工作的,也就是说这个外设的时钟来自于它挂在的总线。例如串口UART挂在PSYS域下的APB总线上,因此串口的时钟来源是PCLK_PSYS。

 

我们可以通过记住和分析上面的这些时钟域和总线的数值,来确定各个外设的具体时钟频率。

 

5、各时钟的典型值(IROM中设置的值)

 

S5PV210之时钟系统_第4张图片

 

(1)当X210刚上电时,默认是外部晶振 + 内部时钟发生器产生的24MHz频率的时钟直接给ARMCLK的,这时系统的主频就是24MHz。

(2)IROM代码执行时,第六步中初始化了时钟系统,这时给了系统一个默认推荐的运行频率,这个时钟频率是三星推荐的S5PV210工作性能和稳定性最佳的频率。

 

二、S5PV210的时钟体系框图

 

S5PV210之时钟系统_第5张图片

S5PV210之时钟系统_第6张图片

 

(1)两张图之间是渐进的关系。第一张图从左到右依次完成了:原始时钟的生成-->PLL倍频得到高频时钟-->初次分频得到各总线时钟,第二张图是从各个中间时钟(第一张图中某个步骤生成的时钟)到各外设自己使用的时钟(实际就是个别外设自己再额外分频的设置)。

(2)第一张图是理解整个时钟体系的关键,第二张图是进一步分析各外设时钟来源的关键。

(3)要看懂时钟体系框图,两个符号很重要:一个是MUX开关,另一个是DIV分频器。

         1)MUX开关就是一个或门,实际对应某个寄存器的某几个bit位的设置,设置值决定了哪条通道是通                    的。分析这个可以知道右边的时钟是从左边哪条路过来的,从而知道右边的时钟是多少。

         2)DIV分频器是一个硬件设备,可以对左边的频率进行分频,分频后的低频时钟输出到右边。分频器在                编程时实际对应某个寄存器中的某几个bit位。我们可以通过设置这个寄存器的对应bit位来设置分频                器的分频系数。

(4)寄存器中的Clock Source Control Register就是用来设置MUX开关的,Clock Divider Control Register就是用来设置DIV分频器的分频系数的。

 

三、时钟设置的关键性寄存器

 

(1)xPLL_LOCK

         主要控制PLL的锁定周期

(2)xPLL_CON、xPLL_CON0、xPLL_CON1

         主要用来打开 / 关闭PLL电路,设置PLL的倍频参数,查看PLL的锁定状态等

(3)CLK_SRCn(n取值为0~6)

         主要用来设置时钟来源,对应时钟体系框图中的MUX开关

(4)CLK_SRC_MASKn

         主要用来决定MUX开关n选1后是否能继续通过

(5)CLK_DIVn

         主要用来设置各模块的分频器参数

(6)CLK_GATE_n

         类似于CLK_SRC_MASKn,对时钟进行开关控制

(7)CLK_DIV_STATn、CLK_MUX_STATn

         主要用来查看DIV和MUX的状态是否已经完成还是在进行中

 

其中最重要的寄存器有三类:CON、SRC、DIV。CON决定PLL倍频到多少,SRC决定MUX走哪一条路,DIV决定分频多少。

 

四、时钟设置的步骤

 

1、步骤分析

 

(1)先选择不使用PLL。让外部的24MHz原始时钟直接过去,绕过APLL那条路

(2)设置锁定时间。

(3)设置分频系数。决定由PLL倍频出来的最高时钟如何分频得到各个分时钟

(4)设置PLL。主要是设置PLL的倍频系数,决定由24MHz的原始时钟可以得到多大的输出频率

(5)打开PLL。前四步已经设置好了所有的开关和分频系数,本步骤打开PLL后,PLL开始工作,锁定频率后输出,然后经过分频得到各个频率。

 

2、CLK_SRC寄存器的设置分析

 

S5PV210之时钟系统_第7张图片

 

CLK_SRC寄存器主要是用来设置MUX开关的,可以先将该寄存器设置为全0,主要是bit0和bit4要设置为0,表示APLL和MPLL暂时都不启用。

 

3、xPLL_LOCK寄存器的设置分析

 

S5PV210之时钟系统_第8张图片

 

xPLL_LOCK寄存器主要是用来设置PLL的锁定周期的,官方的推荐值为0x0FFF。

 

4、CLK_DIV寄存器的设置分析

 

S5PV210之时钟系统_第9张图片

 

我们设置的值是0x14131440,含义分析如下:

 

PCLK_PSYS = HCLK_PSYS / 2

HCLK_PSYS = MOUT_PSYS / 5

PCLK_DSYS = HCLK_DSYS / 2

HCLK_DSYS = MOUT_DSYS / 4

PCLK_MSYS = HCLK_MSYS / 2

HCLK_MSYS = ARMCLK / 5

SCLKA2M = SCLKAPLL / 5

ARMCLK = MOUT_MSYS / 1

 

5、APLL和MPLL设置的关键

 

S5PV210之时钟系统_第10张图片

S5PV210之时钟系统_第11张图片

 

APLL和MPLL设置的关键都是P、M、S这三个值,我们设置的值都来自于官方数据手册的推荐值。

 

五、代码实现

 

1、Makefile

 

led.bin: start.o led.o clock.o
	arm-linux-ld -Ttext 0x0 -o led.elf $^
	arm-linux-objcopy -O binary led.elf led.bin
	arm-linux-objdump -D led.elf > led_elf.dis
	gcc mkv210_image.c -o mkx210
	./mkx210 led.bin 210.bin
	
%.o : %.S
	arm-linux-gcc -o $@ $< -c -nostdlib

%.o : %.c
	arm-linux-gcc -o $@ $< -c -nostdlib

clean:
	rm *.o *.elf *.bin *.dis mkx210 -f

 

2、start.S

 

#define SVC_STACK	0xD0037D80

.global _start					
_start:
	// 初始化时钟
	bl clock_init
	
	ldr sp, =SVC_STACK

	bl led_blink					
	
	b .

 

3、led.c

 

#define GPJ0CON	 0xE0200240
#define GPJ0DAT	 0xE0200244

#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)

void delay(void);

void led_blink(void) {
	rGPJ0CON = 0x11111111;
	
	while(1) {
		// led亮
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
		// 延时
		delay();
		// led灭
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
		// 延时
		delay();
	}
}

void delay(void) {
	volatile unsigned int i = 900000;		
	while (i--);							
}

 

4、clock.S

 

// 时钟控制器基地址
#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

#define APLL_MDIV      	 		0x7d		// 125
#define APLL_PDIV       		0x3         // 3
#define APLL_SDIV       		0x1         // 1

#define MPLL_MDIV				0x29b		// 667
#define MPLL_PDIV				0xc         // 12
#define MPLL_SDIV				0x1         // 1

#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
	
	// 设置各种时钟开关,暂时不使用PLL
	ldr	r1, =0x0

	str	r1, [r0, #CLK_SRC0_OFFSET]				

	// 设置锁定时间
	ldr	r1,	=0x0000FFFF					
	str	r1,	[r0, #APLL_LOCK_OFFSET]				
	str r1, [r0, #MPLL_LOCK_OFFSET]	 				

	// 设置分频
	// 清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]

	// 设置PLL
	// FOUT = MDIV * FIN / (PDIV * 2^(SDIV-1)) = 0x7d * 24 / (0x3 * 2^(1-1)) = 1000MHz
	ldr	r1, =APLL_VAL						
	str	r1, [r0, #APLL_CON0_OFFSET]
	// FOUT = MDIV * FIN / (PDIV * 2^SDIV) = 0x29b * 24 / (0xc * 2^1) = 667MHz
	ldr	r1, =MPLL_VAL						
	str	r1, [r0, #MPLL_CON_OFFSET]

	// 设置各种时钟开关,使用PLL
	ldr	r1, [r0, #CLK_SRC0_OFFSET]
	ldr	r2, =0x10001111
	orr	r1, r1, r2
	str	r1, [r0, #CLK_SRC0_OFFSET]

	mov	pc, lr

 

以下为时钟初始化的C语言实现,只需将时钟初始化的相关代码(clock.S)改为C语言实现(clock.c)即可。

 

// 时钟控制器基地址
#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
 
#define APLL_MDIV               0x7d        // 125
#define APLL_PDIV               0x3         // 3
#define APLL_SDIV               0x1         // 1
 
#define MPLL_MDIV               0x29b       // 667
#define MPLL_PDIV               0xc         // 12
#define MPLL_SDIV               0x1         // 1
 
#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)
 
#define REG_CLK_SRC0    (ELFIN_CLOCK_POWER_BASE + CLK_SRC0_OFFSET)
#define REG_APLL_LOCK   (ELFIN_CLOCK_POWER_BASE + APLL_LOCK_OFFSET)
#define REG_MPLL_LOCK   (ELFIN_CLOCK_POWER_BASE + MPLL_LOCK_OFFSET)
#define REG_CLK_DIV0    (ELFIN_CLOCK_POWER_BASE + CLK_DIV0_OFFSET)
#define REG_APLL_CON0   (ELFIN_CLOCK_POWER_BASE + APLL_CON0_OFFSET)
#define REG_MPLL_CON    (ELFIN_CLOCK_POWER_BASE + MPLL_CON_OFFSET)
 
#define rREG_CLK_SRC0   (*(volatile unsigned int *)REG_CLK_SRC0)
#define rREG_APLL_LOCK  (*(volatile unsigned int *)REG_APLL_LOCK)
#define rREG_MPLL_LOCK  (*(volatile unsigned int *)REG_MPLL_LOCK)
#define rREG_CLK_DIV0   (*(volatile unsigned int *)REG_CLK_DIV0)
#define rREG_APLL_CON0  (*(volatile unsigned int *)REG_APLL_CON0)
#define rREG_MPLL_CON   (*(volatile unsigned int *)REG_MPLL_CON)
 
void clock_init(void) {
    // 设置各种时钟开关,暂时不使用PLL
    rREG_CLK_SRC0 = 0x0;
     
    // 设置锁定时间
    rREG_APLL_LOCK = 0x0000ffff;
    rREG_MPLL_LOCK = 0x0000ffff;
     
    // 设置分频
    rREG_CLK_DIV0 = 0x14131440;
     
    // 设置PLL
    // FOUT = MDIV * FIN / (PDIV * 2^(SDIV-1)) = 0x7d * 24 / (0x3 * 2^(1-1)) = 1000MHz
    rREG_APLL_CON0 = APLL_VAL;
    // FOUT = MDIV * FIN / (PDIV * 2^SDIV) = 0x29b * 24 / (0xc * 2^1) = 667MHz
    rREG_MPLL_CON = MPLL_VAL;
     
    // 设置各种时钟开关,使用PLL
    rREG_CLK_SRC0 = 0x10001111;
}

 

你可能感兴趣的:(嵌入式,时钟)