上一篇,把start.S分析了一遍,这一篇只要分析lowlevel_init这个函数
这个函数有点长,采取分开分析,一点一点的看,不着急,哈哈哈
还没说要分析哪一个的lowlevel_init的,这是有一个小技巧,可以分享一些,一般的lowlevel_init会在两个地方有,一个是CPU哪里的,一个是board那边的
下面的图片是armv7里面有一个lowlevel_init.S文件,有没有发现这个汇编文件没有生产lowlevel_init.o文件,也就是说这个汇编文件没参与编译,所以不是这个。
下面这个是board那边的,为什么选goni,不用我说了把,会发现这个文件夹下,有lowlevel_init.o,说明这个文件夹下的lowlevel_init.S参与了编译。定位是那一个文件的方式有几种,如果有其他方法能定义到也可以。
那我们就可以看看lowlevel_init.S里面的庐山真面目
#include <config.h>
#include <asm/arch/cpu.h>
#include <asm/arch/clock.h>
#include <asm/arch/power.h>
#include "s5pc110.h"
#define UART_UBRDIV_VAL 34
#define UART_UDIVSLOT_VAL 0xDDDD
/*
* Register usages:
*
* r5 has zero always
* r7 has S5PC100 GPIO base, 0xE0300000
* r8 has real GPIO base, 0xE0300000, 0xE0200000 at S5PC100, S5PC110 repectively
* r9 has Mobile DDR size, 1 means 1GiB, 2 means 2GiB and so on
*/
.globl lowlevel_init
lowlevel_init:
/*
lr是连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两种:一是用来保存子程序返回地址二是当异常发生时,
LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。
这里是把函数的返回地址存储到r11中,因为这个函数还有调用其他函数,如果不存储,这个lr的值就会丢,
在c语言中,这个lr的值会入栈,但是现在是汇编阶段,都要自己来管理。
*/
mov r11, lr
看到头文件arch之后才想起来,这个arch是一个软链接文件,它目前会链接到 arch-s5pc1xx,为什么会链接这个文件夹呢?还是要从Makefile分析,这次还是简单分析,Makefile详细分析留到后面,等把弄懂了才详细分析。
顶层Makefile 501行
# If .config is newer than include/config/auto.conf, someone tinkered
# with it and forgot to run make oldconfig.
# if auto.conf.cmd is missing then we are probably in a cleaned tree so
# we execute the config step to be sure to catch updated Kconfig files
include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
@# If the following part fails, include/config/auto.conf should be
@# deleted so "make silentoldconfig" will be re-run on the next build.
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
{ rm -f include/config/auto.conf; false; }
@# include/config.h has been updated after "make silentoldconfig".
@# We need to touch include/config/auto.conf so it gets newer
@# than include/config.h.
@# Otherwise, 'make silentoldconfig' would be invoked twice.
$(Q)touch include/config/auto.conf
就是这一行$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf -f 是指定后面这个文件为Makefile $(MAKE) 是make,
所以这一行就是执行scripts/Makefile.autoconf 这个Makefile文件。
前面的目标和依赖这些就不看了,应该可以看得懂,这个Makefile不难,我们直奔主题
# symbolic links
# If arch/$(ARCH)/mach-$(SOC)/include/mach exists,
# make a symbolic link to that directory.
# Otherwise, create a symbolic link to arch/$(ARCH)/include/asm/arch-$(SOC).
PHONY += create_symlink
# 就是创建符号链接的一个目标
create_symlink:
ifdef CONFIG_CREATE_ARCH_SYMLINK
# KBUILD_SRC我们顶层Makefile是为空,所以不进入这个,进入else
ifneq ($(KBUILD_SRC),)
$(Q)mkdir -p include/asm
$(Q)if [ -d $(KBUILD_SRC)/arch/$(ARCH)/mach-$(SOC)/include/mach ]; then \
dest=arch/$(ARCH)/mach-$(SOC)/include/mach; \
else \
dest=arch/$(ARCH)/include/asm/arch-$(if $(SOC),$(SOC),$(CPU)); \
fi; \
ln -fsn $(KBUILD_SRC)/$$dest include/asm/arch
else
# arch/arm/mach-s5pc1xx/include/mach 这个文件夹不存在,所以进入else
$(Q)if [ -d arch/$(ARCH)/mach-$(SOC)/include/mach ]; then \
dest=../../mach-$(SOC)/include/mach; \
else \
dest=arch-$(if $(SOC),$(SOC),$(CPU)); \
fi; \
# dest = arch-s5pc1xx 下面的链接就是 ln -fsn arch-s5pc1xx arch/arm/include/asm/arch 就是我们上面看到的效果
# -f 强制执行
# -i 交互模式,文件存在则提示用户是否覆盖
# -n 把符号链接视为一般目录
# -s 软链接(符号链接)
ln -fsn $$dest arch/$(ARCH)/include/asm/arch
endif
endif
继续分析
/* r5 has always zero */
mov r5, #0
//这个lowlevel_init支持两种cpu 一种是s5pc100 一种是s5pc110,
//下面就是通过读取S5PC110_PRO_ID这个寄存器来判断,是110还是100
ldr r7, =S5PC100_GPIO_BASE
ldr r8, =S5PC100_GPIO_BASE
/* Read CPU ID */
# S5PC110_PRO_ID 这个宏定义在<asm/arch/cpu.h> 上面已经讲了arch是一个软连接了,所以我们要找的是asm/arch-s5pc1xx/cpu.h
ldr r2, =S5PC110_PRO_ID
ldr r0, [r2]
mov r1, #0x00010000
and r0, r0, r1
cmp r0, r5
beq 100f
ldr r8, =S5PC110_GPIO_BASE
asm/arch-s5pc1xx/cpu.h 这个文件我贴出来,待会还是会用到
#ifndef _S5PC1XX_CPU_H
#define _S5PC1XX_CPU_H
#define S5P_CPU_NAME "S5P"
#define S5PC1XX_ADDR_BASE 0xE0000000
/* S5PC100 */
#define S5PC100_PRO_ID 0xE0000000
#define S5PC100_CLOCK_BASE 0xE0100000
#define S5PC100_GPIO_BASE 0xE0300000
#define S5PC100_VIC0_BASE 0xE4000000
#define S5PC100_VIC1_BASE 0xE4100000
#define S5PC100_VIC2_BASE 0xE4200000
#define S5PC100_DMC_BASE 0xE6000000
#define S5PC100_SROMC_BASE 0xE7000000
#define S5PC100_ONENAND_BASE 0xE7100000
#define S5PC100_PWMTIMER_BASE 0xEA000000
#define S5PC100_WATCHDOG_BASE 0xEA200000
#define S5PC100_UART_BASE 0xEC000000
#define S5PC100_MMC_BASE 0xED800000
/* S5PC110 */
#define S5PC110_PRO_ID 0xE0000000
#define S5PC110_CLOCK_BASE 0xE0100000
#define S5PC110_GPIO_BASE 0xE0200000
#define S5PC110_PWMTIMER_BASE 0xE2500000
#define S5PC110_WATCHDOG_BASE 0xE2700000
#define S5PC110_UART_BASE 0xE2900000
#define S5PC110_SROMC_BASE 0xE8000000
#define S5PC110_MMC_BASE 0xEB000000
#define S5PC110_DMC0_BASE 0xF0000000
#define S5PC110_DMC1_BASE 0xF1400000
#define S5PC110_VIC0_BASE 0xF2000000
#define S5PC110_VIC1_BASE 0xF2100000
#define S5PC110_VIC2_BASE 0xF2200000
#define S5PC110_VIC3_BASE 0xF2300000
#define S5PC110_OTG_BASE 0xEC000000
#define S5PC110_PHY_BASE 0xEC100000
#define S5PC110_USB_PHY_CONTROL 0xE010E80C
/* Read CPU ID */
ldr r2, =S5PC110_PRO_ID
// r2 = 0xE0000000
ldr r0, [r2]
// r2做为地址 取这个地址的值 r0 = 0x43110020
mov r1, #0x00010000
and r0, r0, r1
// and 是与的命令 r0 #0x00010000
cmp r0, r5
// cmp #0x00010000 #0 r5在上面赋值为0
beq 100f
// r0 和 r5不相等 所以不跳转100f 然后把r8 赋值为S5PC110_GPIO_BASE
ldr r8, =S5PC110_GPIO_BASE
100:
// 这个是关于 CPU当前状态的,如果是空闲,有一些操作是不需要做的,但是如果是重新上电或者
//深度睡眠唤醒都需要从头开始执行
/* Turn on KEY_LED_ON [GPJ4(1)] XMSMWEN */
cmp r7, r8
// r7 和 r8 不相等,不跳转
beq skip_check_didle @ Support C110 only
ldr r0, =S5PC110_RST_STAT
// r0 = 0xE010A000
ldr r1, [r0]
// r1 = 不好说 哈哈 还是先看下面的寄存器说明
// 19 18 16 位都对应着一种睡眠方式 所以下面要与一个D D刚好就是 0x1101 正好对应3位
and r1, r1, #0x000D0000
//上面已经说了 判断 19 18 16 位有没有被置1
cmp r1, #(0x1 << 19) @ DEEPIDLE_WAKEUP
// 判断r1 的19 位是否置1 就是判断当前的状态是不是深度空闲,如果是就跳转didle_wakeup
// 不是的话,就继续往下走
beq didle_wakeup
cmp r7, r8
RESET CONTROL REGISTER
继续往下走,空闲跳转的那个函数,以后也会执行到的,先从主线往下走
这是一个点灯的程序,不过我这边的板子不是用gpio_J4的,不过也可以改成,我们自己的点灯程序
skip_check_didle:
addeq r0, r8, #0x280 @ S5PC100_GPIO_J4
addne r0, r8, #0x2C0 @ S5PC110_GPIO_J4
ldr r1, [r0, #0x0] @ GPIO_CON_OFFSET
bic r1, r1, #(0xf << 4) @ 1 * 4-bit
orr r1, r1, #(0x1 << 4)
str r1, [r0, #0x0] @ GPIO_CON_OFFSET
ldr r1, [r0, #0x4] @ GPIO_DAT_OFFSET
bic r1, r1, #(1 << 1)
str r1, [r0, #0x4] @ GPIO_DAT_OFFSET
这一段是配置同步寄存器的,啥同步和不同步啊,不是很清楚。
/* Don't setup at s5pc100 */
beq 100f
/*
* Initialize Async Register Setting for EVT1
* Because we are setting EVT1 as the default value of EVT0,
* setting EVT0 as well does not make things worse.
* Thus, for the simplicity, we set for EVT0, too
*
* The "Async Registers" are:
* 0xE0F0_0000
* 0xE1F0_0000
* 0xF180_0000
* 0xF190_0000
* 0xF1A0_0000
* 0xF1B0_0000
* 0xF1C0_0000
* 0xF1D0_0000
* 0xF1E0_0000
* 0xF1F0_0000
* 0xFAF0_0000
*/
ldr r0, =0xe0f00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xe1f00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xf1800000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xf1900000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xf1a00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xf1b00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xf1c00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xf1d00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xf1e00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xf1f00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
ldr r0, =0xfaf00000
ldr r1, [r0]
bic r1, r1, #0x1
str r1, [r0]
/*
* Diable ABB block to reduce sleep current at low temperature
* Note that it's hidden register setup don't modify it
*/
// 这个寄存器是三星内部的,没有提供出来,就留在这里吧
ldr r0, =0xE010C300
ldr r1, =0x00800000
str r1, [r0]
但是文档描述的内容先贴上,以后明白了再回来修改。
HALF_SYNC_SEL field of ASYNC_CONFIG0~10 registers decides whether to use half or full synchronization for synchronizer, which separates two different clock domains. Setting this field to HIGH selects half synchronizer, which has better performance over full synchronizer. On the contrary, full synchronizer has a better MTBF (Mean Time Between Failure) resulting from crossing clock domains. It is recommended to use full synchronization for stable operation.
这一段是我自己家的,因为我这块开发版用的不是自锁开关,需要把一个引脚置1,才能保持上电,这个是根据每个板子不同来添加的
/* PS_HOLD pin(GPH0_0) set to high */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
ldr r1, [r0]
orr r1, r1, #0x300
orr r1, r1, #0x1
str r1, [r0]
我们硬件是利用了这个XEINT0引脚的,寄存器设置还是要看手册:
根据寄存器的手册来设置相应的位即可。
下面分别是几个初始化操作,关看门狗、设置SRAM、关中断等操作,如果想了解关于寄存器每一个位操作了什么的话,可以自己去查找芯片手册,都很简单的
/* IO retension release */
ldreq r0, =S5PC100_OTHERS @ 0xE0108200
ldrne r0, =S5PC110_OTHERS @ 0xE010E000
ldr r1, [r0]
ldreq r2, =(1 << 31) @ IO_RET_REL
ldrne r2, =((1 << 31) | (1 << 30) | (1 << 29) | (1 << 28))
orr r1, r1, r2
/* Do not release retention here for S5PC110 */
streq r1, [r0]
/* Disable Watchdog */
ldreq r0, =S5PC100_WATCHDOG_BASE @ 0xEA200000
ldrne r0, =S5PC110_WATCHDOG_BASE @ 0xE2700000
str r5, [r0]
/* setting SRAM */
ldreq r0, =S5PC100_SROMC_BASE @0xE7000000
ldrne r0, =S5PC110_SROMC_BASE @0xE8000000
ldr r1, =0x9
str r1, [r0]
/* S5PC100 has 3 groups of interrupt sources */
ldreq r0, =S5PC100_VIC0_BASE @ 0xE4000000
ldrne r0, =S5PC110_VIC0_BASE @ 0xF2000000
add r1, r0, #0x00100000
add r2, r0, #0x00200000
/* Disable all interrupts (VIC0, VIC1 and VIC2) */
mvn r3, #0x0
str r3, [r0, #0x14] @ INTENCLEAR
str r3, [r1, #0x14] @ INTENCLEAR
str r3, [r2, #0x14] @ INTENCLEAR
/* Set all interrupts as IRQ */
str r5, [r0, #0xc] @ INTSELECT
str r5, [r1, #0xc] @ INTSELECT
str r5, [r2, #0xc] @ INTSELECT
/* Pending Interrupt Clear */
str r5, [r0, #0xf00] @ INTADDRESS
str r5, [r1, #0xf00] @ INTADDRESS
str r5, [r2, #0xf00] @ INTADDRESS
分析了这么久,感觉都没几个重要的初始化,都是一些基本的,不过接下里就有重量级的初始化了
系统时钟初始化:
但是不知道为什么,官网的uboot竟然不调用这个函数,所以这个要自己加上,在lowlevel_init.S后面有时钟的初始化,我们简单的分析分析,只要得出我们几个重要的时钟的频率的可以了,没必要全部理解,毕竟是汇编写的。
/* init system clock */
bl system_clock_init
/*
* system_clock_init: Initialize core clock and bus clock.
* void system_clock_init(void)
*/
system_clock_init:
ldr r0, =S5PC110_CLOCK_BASE @ 0xE0100000
/* Check S5PC100 */
cmp r7, r8
bne 110f
100:
/* Set Lock Time */
ldr r1, =0xe10 @ Locktime : 0xe10 = 3600
str r1, [r0, #0x000] @ S5PC100_APLL_LOCK
str r1, [r0, #0x004] @ S5PC100_MPLL_LOCK
str r1, [r0, #0x008] @ S5PC100_EPLL_LOCK
str r1, [r0, #0x00C] @ S5PC100_HPLL_LOCK
/* S5P_APLL_CON */
ldr r1, =0x81bc0400 @ SDIV 0, PDIV 4, MDIV 444 (1333MHz)
str r1, [r0, #0x100]
/* S5P_MPLL_CON */
ldr r1, =0x80590201 @ SDIV 1, PDIV 2, MDIV 89 (267MHz)
str r1, [r0, #0x104]
/* S5P_EPLL_CON */
ldr r1, =0x80870303 @ SDIV 3, PDIV 3, MDIV 135 (67.5MHz)
str r1, [r0, #0x108]
/* S5P_HPLL_CON */
ldr r1, =0x80600603 @ SDIV 3, PDIV 6, MDIV 96
str r1, [r0, #0x10C]
ldr r1, [r0, #0x300]
ldr r2, =0x00003fff
bic r1, r1, r2
ldr r2, =0x00011301
orr r1, r1, r2
str r1, [r0, #0x300]
ldr r1, [r0, #0x304]
ldr r2, =0x00011110
orr r1, r1, r2
str r1, [r0, #0x304]
ldr r1, =0x00000001
str r1, [r0, #0x308]
/* Set Source Clock */
ldr r1, =0x00001111 @ A, M, E, HPLL Muxing
str r1, [r0, #0x200] @ S5PC1XX_CLK_SRC0
b 200f
110:
ldr r0, =0xE010C000 @ S5PC110_PWR_CFG
/* Set OSC_FREQ value */
ldr r1, =0xf
str r1, [r0, #0x100] @ S5PC110_OSC_FREQ
/* Set MTC_STABLE value */
ldr r1, =0xffffffff
str r1, [r0, #0x110] @ S5PC110_MTC_STABLE
/* Set CLAMP_STABLE value */
ldr r1, =0x3ff03ff
str r1, [r0, #0x114] @ S5PC110_CLAMP_STABLE
ldr r0, =S5PC110_CLOCK_BASE @ 0xE0100000
//从这里开始吧,前面几个寄存器也看了,不知道啥
/* Set Clock divider */
//想了解时钟寄存器的各个项,可以看下面详细描述
// ldr r1, =0x14131330 @ 1:1:4:4, 1:4:5
ldr r1, =0x14131430 @ 1:1:4:4, 1:4:5
str r1, [r0, #0x300]
//这是串口有关的时钟,比较重要
ldr r1, =0x11110111 @ UART[3210]: MMC[3210]
str r1, [r0, #0x310]
/* Set Lock Time */
//设置倍频等待时间的
ldr r1, =0x2cf @ Locktime : 30us
str r1, [r0, #0x000] @ S5PC110_APLL_LOCK
ldr r1, =0xe10 @ Locktime : 0xe10 = 3600
str r1, [r0, #0x008] @ S5PC110_MPLL_LOCK
str r1, [r0, #0x010] @ S5PC110_EPLL_LOCK
str r1, [r0, #0x020] @ S5PC110_VPLL_LOCK
/* S5PC110_APLL_CON */
//设置APLL时钟,210APLL设置为1000M,所以需要修改
// ldr r1, =0x80C80601 @ 800MHz
ldr r1, =0x807D0301 @ 1000MHz
str r1, [r0, #0x100]
/* S5PC110_MPLL_CON */
//设置MPLL时钟
ldr r1, =0x829B0C01 @ 667MHz
str r1, [r0, #0x108]
/* S5PC110_EPLL_CON */
//设置EPLL时钟
ldr r1, =0x80600602 @ 96MHz VSEL 0 P 6 M 96 S 2
str r1, [r0, #0x110]
/* S5PC110_VPLL_CON */
//设置VPLL时钟
ldr r1, =0x806C0603 @ 54MHz
str r1, [r0, #0x120]
/* Set Source Clock */
//设置mux的值
ldr r1, =0x10001111 @ A, M, E, VPLL Muxing
str r1, [r0, #0x200] @ S5PC1XX_CLK_SRC0
/* OneDRAM(DMC0) clock setting */
//不知道为什么要选SCLKMPLL这个时钟源
ldr r1, =0x01000000 @ ONEDRAM_SEL[25:24] 1 SCLKMPLL
str r1, [r0, #0x218] @ S5PC110_CLK_SRC6
//SCLK_DMC0 = MOUTDMC0 / (DMC0_RATIO + 1)
ldr r1, =0x30000000 @ ONEDRAM_RATIO[31:28] 3 + 1
str r1, [r0, #0x318] @ S5PC110_CLK_DIV6
/* XCLKOUT = XUSBXTI 24MHz */
add r2, r0, #0xE000 @ S5PC110_OTHERS
ldr r1, [r2]
orr r1, r1, #(0x3 << 8) @ CLKOUT[9:8] 3 XUSBXTI
str r1, [r2]
//下面是设置哪一个时钟开启的掩码的
/* CLK_IP0 */
ldr r1, =0x8fefeeb @ DMC[1:0] PDMA0[3] IMEM[5]
str r1, [r0, #0x460] @ S5PC110_CLK_IP0
/* CLK_IP1 */
ldr r1, =0xe9fdf0f9 @ FIMD[0] USBOTG[16]
@ NANDXL[24]
str r1, [r0, #0x464] @ S5PC110_CLK_IP1
/* CLK_IP2 */
ldr r1, =0xf75f7fc @ CORESIGHT[8] MODEM[9]
@ HOSTIF[10] HSMMC0[16]
@ HSMMC2[18] VIC[27:24]
str r1, [r0, #0x468] @ S5PC110_CLK_IP2
/* CLK_IP3 */
ldr r1, =0x8eff038c @ I2C[8:6]
@ SYSTIMER[16] UART0[17]
@ UART1[18] UART2[19]
@ UART3[20] WDT[22]
@ PWM[23] GPIO[26] SYSCON[27]
str r1, [r0, #0x46c] @ S5PC110_CLK_IP3
/* CLK_IP4 */
ldr r1, =0xfffffff1 @ CHIP_ID[0] TZPC[8:5]
str r1, [r0, #0x470] @ S5PC110_CLK_IP3
200:
/* wait at least 200us to stablize all clock */
mov r2, #0x10000
1: subs r2, r2, #1
bne 1b
mov pc, lr
外部24M时钟进来,就进入倍频电路,PLL这些就是倍频电路,倍频电路输出的就是我们各个模块所需的频率(有的还需要分频)
APLL:Cortex-A8内核 MSYS域(ARMCLK, HCLK_MSYS, and PCLK_MSYS)
MPLL&EPLL:DSYS PSYS(HCLK_DSYS, HCLK_PSYS, PCLK_DSYS, and PCLK_PSYS, peripheral clocks )
VPLL:Video视频相关模块
MSYS域:
ARMCLK: 给cpu内核工作的时钟,也就是所谓的主频。
HCLK_MSYS: MSYS域的高频时钟,给DMC0和DMC1使用
PCLK_MSYS: MSYS域的低频时钟
HCLK_IMEM:给iROM和iRAM(合称iMEM)使用
DSYS域:
HCLK_DSYS:DSYS域的高频时钟
PCLK_DSYS:DSYS域的低频时钟
PSYS域:
HCLK_PSYS:PSYS域的高频时钟
PCLK_PSYS:PSYS域的低频时钟
SCLK_ONENAND:
总结:210内部的各个外设都是接在(内部AMBA总线)总线上面的,AMBA总线有1条高频分支叫AHB,有一条低频分支叫APB。上面的各个域都有各自对应的HCLK_XXX和PCLK_XXX,其中HCLK_XXX就是XXX这个域中AHB总线的工作频率;PCLK_XXX就是XXX这个域中APB总线的工作频率。
SoC内部的各个外设其实是挂在总线上工作的,也就是说这个外设的时钟来自于他挂在的总线,譬如串口UART挂在PSYS域下的APB总线上,因此串口的时钟来源是PCLK_PSYS。
我们可以通过记住和分析上面的这些时钟域和总线数值,来确定我们各个外设的具体时钟频率。(来自朱老师的笔记)
(1)当210刚上电时,默认是外部晶振+内部时钟发生器产生的24MHz频率的时钟直接给ARMCLK的,这时系统的主频就是24MHz,运行非常慢。
(2)iROM代码执行时第6步中初始化了时钟系统,这时给了系统一个默认推荐运行频率。这个时钟频率是三星推荐的210工作性能和稳定性最佳的频率。
(3)各时钟的典型值:
? 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 (来自朱老师的笔记)
xPLL_LOCK
xPLL_LOCK寄存器主要控制PLL锁定周期的。
xPLL_CON/xPLL_CON0/xPLL_CON1
PLL_CON寄存器主要用来打开/关闭PLL电路,设置PLL的倍频参数,查看PLL锁定状态等
CLK_SRCn(n:0~6)
CLK_SRC寄存器是用来设置时钟来源的,对应时钟框图中的MUX开关。
CLK_SRC_MASKn
CLK_SRC_MASK决定MUX开关n选1后是否能继续通过。默认的时钟都是打开的,好处是不会因为某个模块的时钟关闭而导致莫名其妙的问题,坏处是功耗控制不精细、功耗高。
CLK_DIVn
各模块的分频器参数配置
CLK_GATE_x
类似于CLK_SRC_MASK,对时钟进行开关控制
CLK_DIV_STATn
CLK_MUX_STATn
这两类状态位寄存器,用来查看DIV和MUX的状态是否已经完成还是在进行中
总结:其中最重要的寄存器有3类:CON、SRC、DIV。其中CON决定PLL倍频到多少,SRC决定走哪一路,DIV决定分频多少。(来自朱老师笔记,朱老师总结的很不错的,有兴趣学习的可以去看看朱老师的视频)
然后我们把CLK_DIV0寄存器的公式,提取出来
PCLK_PSYS = HCLK_PSYS / (1 + 1) = 133M / 2 = 66M
HCLK_PSYS = MOUT_PSYS / (4+ 1) = 667M / 5 = 133M
PCLK_DSYS = HCLK_DSYS / (1+ 1) = 166M /2 = 83M
HCLK_DSYS = MOUT_DSYS / (3+ 1) = 667M /4 = 166M
PCLK_MSYS = HCLK_MSYS / (1+ 1) = 200M / 2 =100M
HCLK_MSYS = ARMCLK / (3 + 1) = 1000M / 4 = (修改成5 才是200M)= 1000M /5 = 200M
SCLKA2M = SCLKAPLL / (3+ 1)
ARMCLK = MOUT_MSYS / (0+ 1) = APLL/1 = 1000M
我们看到公式里面还有两个未知数,这个下面会有列出,然后继续让下走,算出来跟默认值一样。完美
然后我们把CLK_DIV4寄存器的公式,提取出来
SCLK_UART3 = MOUTUART3 / (1 + 1)
SCLK_UART2 = MOUTUART2 / (1+ 1)
SCLK_UART1 = MOUTUART1 / (1+ 1)
SCLK_UART0 = MOUTUART0 / (1+ 1)
SCLK_MMC3 = MOUTMMC3 / (0 + 1)
SCLK_MMC2 = MOUTMMC2 / (1+ 1)
SCLK_MMC1 = MOUTMMC1 / (1+ 1)
SCLK_MMC0 = MOUTMMC0 / (1+ 1)
这个是设置APLL时钟频率的:
FOUT = MDIV X FIN / (PDIV × 2SDIV-1)
我这块210板子的晶振频率是24M
FOUT=0xc8×24M/(0x06 × 21-1)
FOUT=200×24M/6=800M(修改后为1000M)
还是继续算MPLL时钟频率:
FOUT = MDIV X FIN / (PDIV × 2SDIV)
我这块210板子的晶振频率是24M
FOUT=0x29B×24M/(0x0C × 21)
FOUT=667×24M/(12*2)=667M
还是继续算EPLL时钟频率:
FOUT = (MDIV+K/65536) X FIN / (PDIV X 2SDIV)
我这块210板子的晶振频率是24M
FOUT= (0x60+0)×24M/(0x06x22)
FOUT=96×24M/(6×4)=96M
还是继续算VPLL时钟频率:
FOUT = MDIV X FIN / (PDIV × 2SDIV)
我这块210板子的晶振频率是24M
FOUT=0x6C×24M/(0x06 × 23)
FOUT=108×24M/(6*8)=54M
总结:
APLL = 800M
MPLL = 667M
EPLL = 96M
VPLL = 54M
大总结:
上图就是根据寄存器设置的值,来计算出每一个时钟的频率
PCLK_PSYS = HCLK_PSYS / (1 + 1) = 133M / 2 = 66M
HCLK_PSYS = MOUT_PSYS / (4+ 1) = 667M / 5 = 133M
PCLK_DSYS = HCLK_DSYS / (1+ 1) = 166M /2 = 83M
HCLK_DSYS = MOUT_DSYS / (3+ 1) = 667M /4 = 166M
PCLK_MSYS = HCLK_MSYS / (1+ 1) = 200M / 2 =100M
HCLK_MSYS = ARMCLK / (3 + 1) = 1000M / 4 = (修改成5 才是200M)= 1000M /5 = 200M
SCLKA2M = SCLKAPLL / (3+ 1)
ARMCLK = MOUT_MSYS / (0+ 1) = APLL/1 = 1000M
时钟相关的就介绍到这里,有兴趣的可以去看一看数据手册中的时钟部分,一个CPU的核心是时钟,搞懂了时钟树,很多东西都会清楚明白。
继续往下走
不知道官网的uboot的串口写的是啥,所以做了一些修改
/* for UART */
bl uart_asm_init
*
* uart_asm_init: Initialize UART's pins
*/
uart_asm_init:
/* set GPIO to enable UART0-UART4 */
//把GPIO设置成串口的模式
mov r0, r8
ldr r1, =0x22222222
str r1, [r0, #0x0] @ S5PC100_GPIO_A0_OFFSET
ldr r1, =0x00002222
str r1, [r0, #0x20] @ S5PC100_GPIO_A1_OFFSET
/* Check S5PC100 */
cmp r7, r8
bne 110f
/* UART_SEL GPK0[5] at S5PC100 */
add r0, r8, #0x2A0 @ S5PC100_GPIO_K0_OFFSET
ldr r1, [r0, #0x0] @ S5PC1XX_GPIO_CON_OFFSET
bic r1, r1, #(0xf << 20) @ 20 = 5 * 4-bit
orr r1, r1, #(0x1 << 20) @ Output
str r1, [r0, #0x0] @ S5PC1XX_GPIO_CON_OFFSET
ldr r1, [r0, #0x8] @ S5PC1XX_GPIO_PULL_OFFSET
bic r1, r1, #(0x3 << 10) @ 10 = 5 * 2-bit
orr r1, r1, #(0x2 << 10) @ Pull-up enabled
str r1, [r0, #0x8] @ S5PC1XX_GPIO_PULL_OFFSET
ldr r1, [r0, #0x4] @ S5PC1XX_GPIO_DAT_OFFSET
orr r1, r1, #(1 << 5) @ 5 = 5 * 1-bit
str r1, [r0, #0x4] @ S5PC1XX_GPIO_DAT_OFFSET
b 200f
110:
/*
* 下面是做了修改之后的串口初始化
*/
//串口寄存器的基地址(宏会根据选择的串口指定不同的基地址)
ldr r0, =ELFIN_UART_CONSOLE_BASE @0xEC000000
mov r1, #0x0
str r1, [r0, #UFCON_OFFSET] //0x08
str r1, [r0, #UMCON_OFFSET] //0x0C
mov r1, #0x3
str r1, [r0, #ULCON_OFFSET] //0x00
//UCON寄存器的Clock Selection [10]代表选择时钟的,0选PCLK,1选择SCLK_UART,这是选择了PCLK
ldr r1, =0x3c5
str r1, [r0, #UCON_OFFSET] //0x04
//下面两个寄存器是计算波特率的,PCLK_PSYS=66M
//计算公式:DIV_VAL = (PCLK / (bps x 16)) −1
//DIV_VAL = 66000000 / (115200 × 16) - 1 = 34.8
//UART_UBRDIV_VAL = UBRDIVn = 34
//UDIVSLOTn/16 = 0.8 UDIVSLOTn = 12.8
//这个12.8需要查表,才能得出结论
//表在下面,查表得出12 为 0xDDDD 13为0xDFDD 两个都可以,任选一个
ldr r1, =UART_UBRDIV_VAL //34
str r1, [r0, #UBRDIV_OFFSET] //0x28
ldr r1, =UART_UDIVSLOT_VAL //0xDDDD
str r1, [r0, #UDIVSLOT_OFFSET] //0x2C
ldr r1, =0x4f4f4f4f
str r1, [r0, #UTXH_OFFSET] @'O'
mov pc, lr
200:
mov pc, lr
搞了这么久,也要输出一些成果,有图有真想
其实应该会打印K的,不知道为什么在SDRAM那里卡死了,所以只打了一个O,不过很符合我们这一讲的内容,这一讲就是输出O,下一节会好好分析下SDRAM的初始化和重映射。
这一次讲的比较多,写这篇文章时间跨度也有点大,这次的内容需要代码和数据手册相互切换,确实不好通过文字描述出来,难免有错误的地方,如果哪里讲的不对,可以留言联系我,再做修改