2015.10uboot移植笔记 (三、低级初始化lowlevel_init)

上一篇,把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

简单介绍一下210的时钟系统

1. 时钟域

主时钟域 (MSYS):CPU(Cortex A8内核)、DRAM (DMC0 and DMC1), internal SRAM (IRAM, and IROM)

显示时钟域 (DSYS):显示相关模块,包括FIMC,FIMD,JPEG

外设系统时钟域 (PSYS):和内部的各种外设时钟有关,譬如串口、SD接口、I2C、AC97、USB等。

时钟域有一张图片介绍
在这里插入图片描述

2. 时钟源

s5pv210有4个输入时钟源

RTC时钟源(32.768K)

XTI (24M)

USB时钟源 (24M)

HDMI时钟源(27M)

我这块210y用的是XTI时钟源,24M
在这里插入图片描述

3.PLL:APLL、MPLL、EPLL、VPLL

外部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视频相关模块

4.S5PV210时钟域详解

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。
我们可以通过记住和分析上面的这些时钟域和总线数值,来确定我们各个外设的具体时钟频率。(来自朱老师的笔记)

5.各时钟典型值(默认值,iROM中设置的值)

(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 (来自朱老师的笔记)

6.时钟设置的关键性寄存器

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决定分频多少。(来自朱老师笔记,朱老师总结的很不错的,有兴趣学习的可以去看看朱老师的视频)

7.分析代码

Clock Divider Control Register (CLK_DIV0, R/W, Address = 0xE010_0300)

在这里插入图片描述
然后我们把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

我们看到公式里面还有两个未知数,这个下面会有列出,然后继续让下走,算出来跟默认值一样。完美

Clock Divider Control Register (CLK_DIV4, R/W, Address = 0xE010_0310)

在这里插入图片描述
然后我们把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)

PLL Control Registers (APLL_LOCK / MPLL_LOCK / EPLL_LOCK / VPLL_LOCK)

在这里插入图片描述

这个是设置APLL时钟频率的:
FOUT = MDIV X FIN / (PDIV × 2SDIV-1)
我这块210板子的晶振频率是24M
FOUT=0xc8×24M/(0x06 × 21-1)
FOUT=200×24M/6=800M(修改后为1000M)

PLL Control Registers (MPLL_CON, R/W, Address = 0xE010_0108)

在这里插入图片描述
还是继续算MPLL时钟频率:
FOUT = MDIV X FIN / (PDIV × 2SDIV)
我这块210板子的晶振频率是24M
FOUT=0x29B×24M/(0x0C × 21)
FOUT=667×24M/(12*2)=667M

PLL Control Registers (EPLL_CON0/ EPLL_CON1, R/W, Address = 0xE010_0110/0xE010_0114)

在这里插入图片描述
还是继续算EPLL时钟频率:
FOUT = (MDIV+K/65536) X FIN / (PDIV X 2SDIV)
我这块210板子的晶振频率是24M
FOUT= (0x60+0)×24M/(0x06x22)
FOUT=96×24M/(6×4)=96M

PLL Control Registers (VPLL_CON, R/W, Address = 0xE010_0120)

在这里插入图片描述
还是继续算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

Clock Source Control Registers (CLK_SRC0, R/W, Address = 0xE010_0200)

在这里插入图片描述
大总结:
在这里插入图片描述
上图就是根据寄存器设置的值,来计算出每一个时钟的频率
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的初始化和重映射。

这一次讲的比较多,写这篇文章时间跨度也有点大,这次的内容需要代码和数据手册相互切换,确实不好通过文字描述出来,难免有错误的地方,如果哪里讲的不对,可以留言联系我,再做修改

发布了32 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/C1033177205/article/details/90576075