上篇讲了配置时钟的原理,今天就结合源码具体分析一下。在U-Boot的源码中,系统时钟的初始化是放在板文件夹下的(board/Samsung/mytiny4412)的clock_init_zthtiny4412.S文件中的system_clock_init函数中。我们的Tiny4412是拷贝的smdk4212来的,所以,大家可以先参看smdk4212的clock_init_smdk4212.S文件中的system_clock_init函数来分析,完了再修改适合自己板子的程序。
对于芯片手册中每一个模块的学习,在了解前面的基本原理后,关键的寄存器的操作,可以先浏览一下这个模块的所有寄存器的简介,对各个寄存器的作用做到心中有数,这样,可以大体知道,需要配置哪些寄存器,不至于被下面一大堆的寄存器给弄懵了,系统时钟的寄存器介绍在第776开始的第7.9节REGISTERDESCRIPTION 。
我这里直接参照了FriendlyARM所提供的U-BOOT源码,基本上是复制了其tiny4412目录下的时钟设置代码,说明一下,我是学习为目录,所以很多代码都是从现有可以运行的程序那里复制来的,换句话说,我现在基本是在注释代码吧,下面顺着函数来分析:
一、输入源和分频比
设置时钟模块的时钟输入源和分频比,包括CPU,DMC,TOP,LEFTBUS,RIGHTBUS等。
1、CPU MUX /DIV的时钟源和分频比
由于要设置的模块寄存器比较多,这里我他仅仅分析一下如何设置 CPU MUX /DIV的时钟源和分频比,其设置过程是怎么样的。
开如之前,修改代码开头:
#ifdef CONFIG_EXYNOS4412
#include"smdk4412_val.h"à#include "zthtiny4412_val.h"
#else
#include"smdk4212_val.h"à#include "zthtiny4212_val.h"
#endif
再来看这个图3-1
图4-1、时钟源选择图
以及其设置寄存器如下表4-1截图所示:
表4-1、CLK_SRC_CPU寄存器设置
为了用24M的外部时钟进行分频,需设置MUX_APLL_SEL(MUXAPLL)为0,那么MUX_CORE_SEL也需设置成0,选择MOUTAPLL.其它的两位,按代码以前的设置为0,分别将MUX_MPLL_USER_SEL_C设置选择FINPLL.而MUX_HPM_SEL选择MOUTAPLL。所以此寄存器的设置值为0。设置完成后需要等待一定时间,让其设置成功,代码的实现方式是也可以与寄存器CLK_MUX_STAT_CPU的值相比较。如下表4-2所示为CLK_MUX_STAT_CPU状态值说明。
表4-2、CLK_MUX_STAT_CPU状态值说明
结合上表的设置值,设置完成后,MUX_MPLL_USER_SEL_C设置选择FINPLL那么26:24位的值应为001,而MUX_HPM_SEL选择MOUTAPLL,那么26:24位的值也应为001,同理,可分析出其他位的值,那么,此寄存器的值应为0x0111_0001下面来看看代码具体写法:
system_clock_init:
push {lr}@将链接寄存器压栈
ldr r0,=ELFIN_CLOCK_BASE @ELFIN_CLOCK_BASE=0x1003_0000 ,时钟寄存器的基地址
@CMU_CPU MUX / DIV
ldr r1,=0x0
ldr r2,=CLK_SRC_CPU_OFFSET@ CLK_SRC_CPU寄存器的偏移地址
str r1,[r0, r2]@ CLK_SRC_CPU寄存器的设置为0
ldr r2, =CLK_MUX_STAT_CPU_OFFSET@CLK_MUX_STAT_CPU寄存器的偏移地址
ldr r3, =0x01110001@需要比较的CLK_MUX_STAT_CPU设置值,即我们上面分析的值。
bl wait_mux_state @跳转到wait_mux_state等待寄存器值设置成功
wait_mux_state:
ldr r1, [r0, r2]@读取CLK_MUX_STAT_CPU寄存器的值
cmp r1, r3@ 将CLK_MUX_STAT_CPU寄存器的值和0x01110001进行比较
bne wait_mux_state@不等就再来一次
mov pc, lr@等了就返回。
@也可以用以下的方法来等待一定的时间
@/* wait ?us */
@ mov r1, #0x10000
@1: subs r1, r1, #1
@ bne 1b
好了,至此我们分析了如何设置 CPUMUX /DIV的时钟源选择的代码,但其分频比且没有进行设置,其实设置分频比的代码在后面设置完成LOCKOUT时间后才进行的,至于为什么这么安排我也不清楚,我们顺着代码分析就行,那这段代码还是在后面进行说明。
下面的代码是接着设置DMC,TOP,LEFTBUS,RIGHTBUS等寄存器,这里不做过多分析,以后我会给出详细源码,寄存器的值,针对我们板子是要进行必要修改的,重要的地方我还是会说明,如何查看手册就不做过多说明,接着说。
2、CMU_DMC MUX / DIV设置
这一段代码仅仅设置了其分频值,而其MUX值且没有设置,利用芯片启动的时的默认设置值,大家可以查看手册细细分析。这里参照tiny4412的Uboot进行了其DIV值设置。为什么取用这个值,我一时也还没有弄清楚,这里先留个问号,稍后再做说明。
@ CMU_DMC MUX / DIV add by zth
@CLK_DIV_DMC1_VAL --Tiny4412_val.h
@defined(CONFIG_CLK_BUS_DMC_100_200)->CLK_DIV_DMC1_VAL=0X00113113
@CLK_DIV_DMC1_VAL=0X00111113
ldr r1,=CLK_DIV_DMC0_VAL
ldr r2,=CLK_DIV_DMC0_OFFSET
str r1,[r0, r2]
ldr r1,=CLK_DIV_DMC1_VAL @CLK_DIV_DMC1_VAL=0x07071713
ldr r2,=CLK_DIV_DMC1_OFFSET
str r1,[r0, r2]
3、CMU_TOP MUX / DIV设置
CMU_TOP的MUX寄存器是CLK_SRC_TOP0和CLK_SRC_TOP1,他们的设置值分别由原值0X0变为0X00000110和0x01111000,分别利用比较其相应的状态寄存器CLK_MUX_STAT_TOP和CLK_MUX_STAT_TOP1的方法来确定值是否设置成功,当然也可以用等待一段时间的方法。
最后设置CLK_DIV_TOP寄存器,其值为0x01215474
@CMU_TOP MUX / DIV
@CLK_SRC_TOP0_VAL= 0x00000110
ldr r1,=CLK_SRC_TOP0_VAL
ldr r2,=CLK_SRC_TOP0_OFFSET
str r1,[r0, r2]
ldr r2, =CLK_MUX_STAT_TOP_OFFSET
@ldr r3, =0x11111111
ldr r3, =CLK_MUX_STAT_TOP_VAL @0x11111221
bl wait_mux_state
@CLK_SRC_TOP1_VAL= 0x01111000
ldr r1,=CLK_SRC_TOP1_VAL
ldr r2,=CLK_SRC_TOP1_OFFSET
str r1,[r0, r2]
ldr r2, =CLK_MUX_STAT_TOP1_OFFSET
@ldr r3, =0x01111110
ldr r3, =CLK_MUX_STAT_TOP1_VAL @0x02222110
bl wait_mux_state
@/* wait ?us */
@ mov r1, #0x10000
@1: subs r1, r1, #1
@ bne 1b
ldr r1,=CLK_DIV_TOP_VAL @0x01215474
ldr r2, =CLK_DIV_TOP_OFFSET
str r1,[r0, r2]
4、CMU_LEFTBUS MUX / DIV设置
CMU_LEFTBUSCMU的寄器设置和上述过程一样,这里不做过多说明,这里仅给出代码
ldr r1,=CLK_SRC_LEFTBUS_VAL
ldr r2,=CLK_SRC_LEFTBUS_OFFSET
str r1,[r0, r2]
ldr r2, =CLK_MUX_STAT_LEFTBUS_OFFSET
@ldr r3, =0x00000021
ldr r3, =CLK_MUX_STAT_LEFTBUS_VAL@0x00000021
bl wait_mux_state
ldr r1,=CLK_DIV_LEFRBUS_VAL
ldr r2,=CLK_DIV_LEFTBUS_OFFSET
str r1,[r0, r2]
5、CMU_RIGHTBUS MUX / DIV设置
CMU_LEFTBUS CMU的寄器设置和上述过程一样,这里不做过多说明,这里仅给出代码.
@ CMU_RIGHTBUS MUX / DIV
ldr r1,=CLK_SRC_RIGHTBUS_VAL
ldr r2,=CLK_SRC_RIGHTBUS_OFFSET
str r1,[r0, r2]
ldr r2, =CLK_MUX_STAT_RIGHTBUS_OFFSET
@ldr r3, =0x00000021
ldr r3, =CLK_MUX_STAT_RIGHTBUS_VAL @0x00000021
bl wait_mux_state
ldr r1,=CLK_DIV_RIGHTBUS_VAL
ldr r2,=CLK_DIV_RIGHTBUS_OFFSET
str r1,[r0, r2]
二、设置APLL/MPLL/EPLL/EPLL锁相环锁频时间
翻看手册,P371页,找到PLLCONTROL REGISTERS。
• (APLL_LOCK, R/W, Address =0x1004_0000)
• (MPLL_LOCK, R/W, Address =0x1004_0000)
• (EPLL_LOCK, R/W, Address =0x1003_0000)
• (VPLL_LOCK, R/W, Address =0xE010_0020)
表4-3、PLL寄存器锁定时间
这里出现了一个问题,APLL/MPLL/EPLL/VPLL的锁相环的时间是不一样的,而像S5PC100,频率为667MHz,他的A/M/E/HPLL的锁相环时间均是300us,这个时间一定要查芯片手册,如下截图的表3-4所示每一个寄存器的设置值,里面有完整的说明:
表4-4、xPLL_LOCK寄存器说明
参看上述说明,用如下宏定义先说明设置值
#if defined(CONFIG_CLK_ARM_800_APLL_800)
#define APLL_PDIV 0x3
#if defined(CONFIG_CLK_BUS_DMC_165_330)
#define MPLL_PDIV 0x5
#elifdefined(CONFIG_CLK_BUS_DMC_200_400)
#define MPLL_PDIV 0x3
#elif defined(CONFIG_CLK_BUS_DMC_220_440)
#define MPLL_PDIV 0x3
#endif
#define EPLL_PDIV 0x2
#define VPLL_PDIV 0x3
/* APLL_LOCK */
#define APLL_LOCK_VAL (APLL_PDIV * 270)/* 810*/
/* MPLL_LOCK */
#define MPLL_LOCK_VAL (MPLL_PDIV * 270)
/* EPLL_LOCK */
#define EPLL_LOCK_VAL (EPLL_PDIV * 3000)
/* VPLL_LOCK */
#define VPLL_LOCK_VAL (VPLL_PDIV * 3000)
接着进行设置,设置方法代码如下,代码的书写方式很明了,这里就不做过多说明。
@ Set PLL locktime
ldr r1,=APLL_LOCK_VAL
ldr r2,=APLL_LOCK_OFFSET
str r1,[r0, r2]
ldr r1,=MPLL_LOCK_VAL
ldr r2,=MPLL_LOCK_OFFSET
str r1,[r0, r2]
ldr r1,=EPLL_LOCK_VAL
ldr r2,=EPLL_LOCK_OFFSET
str r1,[r0, r2]
ldr r1,=VPLL_LOCK_VAL
ldr r2,=VPLL_LOCK_OFFSET
str r1,[r0, r2]
在这段代码后才是设置MCU_CPU的分频比的代码。设置值可以查看
P585 页7.9.1.136CLK_DIV_CPU0和7.9.1.137 CLK_DIV_CPU1页的寄存器列表。
由于zthtiny4412_val.h中可以设置其值,这里修改设置的值如下所示,这两个值的设置得以照APLL后的频来决定。不能随便设置,APLL设置好后,即可以知道APLL频率,此后根据CLK_DIV_CPU0和CLK_DIV_CPU1的说明来计算出所需的值进行设置即可,zthtiny4412_val.h中有设置好几类时钟频率的值,如APLL为800MHZ,1000MHZ时的值不一样。
@Set MCU_CPU Ratio
ldr r1,=CLK_DIV_CPU0_VAL
ldr r2,=CLK_DIV_CPU0_OFFSET
str r1,[r0, r2]
ldr r1,=CLK_DIV_CPU1_VAL
ldr r2,=CLK_DIV_CPU1_OFFSET
str r1,[r0, r2]
三、下面的代码就是开始倍频
1、倍频APLL
这个倍频值需根据需求参考手册来时行设置。这里有不同频进行设置,每一个设置值不做过多说明,仅仅说明一下参考位置,设置值P,M,S可以设置寄存器APLL_CON0,如下图所示,其中,其上电初始化后,如果外部的时钟晶振是24MHZ的话,那么其频率为800MHZ。但需要注意的是设置的输出频率范围在21.9MHZ到1400MHZ
且P,M,S的范围在以下的区间如下表4-5所示。
表4-5、APLL_CON0设置寄存器值
设置值的可取值,可以查看手册447页,表7-2所示。
查此表当设置值P为3,M为175,S为0时,即可让输出频率为1400MHz.
大家一定会注意到代码里对APLL_CON1也进行了设置,那么APLL_CON1主要设置什么呢,查看手册581页,说实话很多设置值是什么意思我也完全不明白,那么我们怎么设置呢,APLL_CON1说明后面一列给出了默认值,OK,那么我设置的就是这个值,其实如果我们设置成默认值,系列上电就是这个值,应可以根本不用这段代码,直接注释了也行吧?
2、倍频MPLL
在倍频MPLL之前,有一小段代码是读取MPLL_CON0的值,来断定其设置值是否已将MPLL设置成不是400MHZ,如果是就不在进行MPLL设置直接调过,进行EPLL设置。这一段代码如下:
/* check MPLL and if MPLL is not 400 Mhzskip MPLL resetting for C2C operation */
ldr r2,=MPLL_CON0_OFFSET
ldr r1, [r0, r2]
ldr r3, =0xA0640301
cmp r1, r3
bne skip_mpll
上数代码中的魔数为什么是0xA0640301,大家可以对照寄存器设置值,以400MHZ为目标进行设置,即可明白为什么是这个值。即将P,M,S分别设置成100,3,1,然后使能PLL就是这个值。
下面接着倍频MPLL的代码、MPLL的设置寄存器为MPLL_CON0和MPLL_CON1,其中MPLL_CON1设置值保持上电初始化值即可。
@ Set MPLL
ldr r1,=MPLL_CON1_VAL
ldr r2,=MPLL_CON1_OFFSET
str r1,[r0, r2]
ldr r1,=MPLL_CON0_VAL
ldr r2,=MPLL_CON0_OFFSET
str r1,[r0, r2]
skip_mpll:
3、倍频EPLL和VPLL
EPLL和VPLL的P/M/S位配置查找方法同上面一样,但要注意的是EPLL和VPLL分别有三个寄存器需要设置,其中EPLL_CON1、EPLL_CON2、APLL_CON1、EPLL_CON2都保持其上电默认值即可。
他们两个寄存器的设置代码如下:
skip_mpll:
@ Set EPLL
ldr r1, =EPLL_CON2_VAL
ldr r2, =EPLL_CON2_OFFSET
str r1, [r0, r2]
ldr r1, =EPLL_CON1_VAL
ldr r2, =EPLL_CON1_OFFSET
str r1, [r0, r2]
ldr r1, =EPLL_CON0_VAL
ldr r2, =EPLL_CON0_OFFSET
str r1, [r0, r2]
@ Set VPLL
ldr r1, =VPLL_CON2_VAL
ldr r2, =VPLL_CON2_OFFSET
str r1, [r0, r2]
ldr r1, =VPLL_CON1_VAL
ldr r2, =VPLL_CON1_OFFSET
str r1, [r0, r2]
ldr r1, =VPLL_CON0_VAL
ldr r2, =VPLL_CON0_OFFSET
str r1, [r0, r2]
下面一代代码相当于等待设置,其实现的方法就是等待xPLL_CON0的第29位自动在LOCKOUT时间完后变为1。
wait_pll_lock:
ldrr1, [r0, r2]
tstr1, #(1<<29)
beqwait_pll_lock
movpc, lr
ldrr2, =APLL_CON0_OFFSET
blwait_pll_lock
ldrr2, =MPLL_CON0_OFFSET
blwait_pll_lock
ldrr2, =EPLL_CON0_OFFSET
blwait_pll_lock
ldrr2, =VPLL_CON0_OFFSET
blwait_pll_lock
在分频之前,必须要先选择时钟源,否则怎么分频呢?接下的来代码再一次进行时钟源选择。
4、选择时钟源
选择时钟源的寄存器设置方法和前面介绍的一样,只是这里需根据新的时钟源选择对相应的寄存器值,进行设置,
这一次我们需要选择倍频后的时钟,不然还选择输入的24MHZ时钟能做什么呢?例如CLK_SRC_CPU寄存器,参考芯片手册时钟生成电路框图以APLL以例,查找FOUTAPLL和FINPLL,由图所知,FINPLL是没有倍频的频率,我们要选的是倍频后的,所以应该选择FOUTAPLL,所以寄存器CLK_SRC_CPU的第0位MUX_APLL_SEL应设置成1.同理我们将第24位MUX_MPLL_USER_SEL_C也设置成1,先择倍频后的MPLL,这样,这个寄存器的设置值为0x01000001
图4-2、APPL时钟源选择说明图
表4-6、CLK_SRC_CPU说明
设置完成后,同样读取相应的状态寄存器,和其对应的值进行比较,以确定是否完成设置。状态寄存器的值应是多少,怎么来的这里不再进行说明,大家可以参考前文所述。完成的代码如下:
@reset the clock's souce.
ldr r1, =0x01000001
ldr r2, =CLK_SRC_CPU_OFFSET
str r1, [r0, r2]
ldrr2, =CLK_MUX_STAT_CPU_OFFSET
ldrr3, =0x02110002
blwait_mux_state
其他寄存器的设置方法类似,这真是一个很烦琐的工作,希望大家耐心一点,细心一点,一次设置通过,不要因为一位的差错,没有得到相应的输出,还得再来一次,这里这么写也劝勉一下我自己。关于后面设置过程中可能会碰到ONENAND的时钟选择问题,ONENAND用的是什么CLK,在Tiny4412中是没有接ONENADN的吧,我们用的是NANDFLASH,因此,这一位应该可以不配置也行,保持默认值吧。
这里说个插曲,这里有一小段代码如下所示,其功能是用来确定所设置的芯片是不是Exynos4412,是就接着设置,不是就直接跳到310_2处执行,这一位置也是此函数的返回位置。
ldr r0, =CHIP_ID_BASE @ 0x10000000
ldr r1, [r0]
lsr r1, r1, #8
and r1, r1, #3
cmp r1, #2
bne v310_2
选择完成时钟源后,其实就可以开始进行分频了,但是由于前面的代码我们已将每个相关的寄存器进行了设置,这里就没有必要再进行一次分频设置了。如果大家想看一个系统的过程,可以参考网友“南山一梦”的博文。我是顺着代码分析说明的,过程可能和他的博文设置方式的不太一样,我也是初学,其中很多也不明白,这么写也是希望自己能记住这个过程。下面还是顺着代码说吧,下面的代码是进行C2C(chipto chip)的设置,C2C是什么呢,手册第40章有明确的说明,大家可以好好的看看。
5、Chip to chip /C2C设置
这里需要我们先定义一个宏变量,如果定义了
#ifdef CONFIG_C2C
宏变量,如果定义了这个宏变量再进行C2C设置。
进行C2C设置的第一步是检验第8章中C2C_CTRL的寄存器最后一位是不是为1,检验其是否已经使能了C2C功能,如果使能了就直接完成设置,因为第8章有说明,如果使能了这个值,那个动态内存管理就会利用其默认值来进行自动管理内存。所应就不用单独设置不同的模块了。
如果没有使能就继续进行设置。
其代码如下:
/* check C2C_CTRL enable bit */
ldr r3,=S5PV310_POWER_BASE@0x1002_0000 chapter8 page644
ldr r1, [r3, #C2C_CTRL_OFFSET]@C2C_CTRL_OFFSET=24
and r1, r1, #1
cmp r1, #0
bne v310_2
接下来的代码是设置几个相关动态内存管理寄存器。要明白这些代码是如何设置的,弄清楚APB总线等协议,我现在刚开如学,想早点走完一次UBOOT移植工作,这里对这段代码不做过多说明,留下个遗憾,改天再来补充完整。