3.u-boot-2016.03 修改时钟、设置内存控制器、支持UART
从第一节新建单板最后的测试结果可知,把编译好的u-boot.bin烧写到Jz2440开发板的Nor Flash并重启开发板,串口没有任何输出,在这一节我们将解决这个问题。这一节的内容主要分为三个部分:修改时钟、修改内存控制器设置、修改配置支持UART。
3.1 修改时钟
通过对u-boot-2016.03启动过程的分析,在start.S文件中发现不足,如下图所示:
从图中可知:uboot里先以60MHZ的时钟计算参数来设置内存控制器,但是MPLL 还未设置;
处理措施:把MPLL的设置放到start.S里,取消board_early_init_f里对MPLL的设置,同时根据新设置的时钟参数设置内存控制器。
关于Jz2440开发板SOC的时钟体系,可以阅读韦东山老师的博客掌握Jz2440_ARM芯片时钟体系。
下面修改代码,把时钟修改为FCLK=400MHz,并设置时钟比例FCLK:HCLK:PCLK=1:4:8,把上图设置时钟比例参数的代码删掉,添加如下代码:
/* 2. 设置时钟 */
ldr r0, =0x4c000014
mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8
str r1, [r0]
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */
orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */
mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
/* MPLLCON = S3C2440_MPLL_200MHZ */
ldr r0, =0x4c000004
ldr r1, =S3C2440_MPLL_400MHZ
str r1, [r0]
/* 启动ICACHE */
mrc p15, 0, r0, c1, c0, 0 @ read control reg
orr r0, r0, #(1<<12)
mcr p15, 0, r0, c1, c0, 0 @ write it back
3.2 修改内存控制器设置
(1) uboot原理是依据HCLK=60MHz的时钟计算参数来设置内存控制器的,修改时钟后HCLK=100MHz,所以需要重新设置内存控制器。在board/samsung/jz2440/lowlevel_init.S中最后有如下代码:
SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
更改为如下代码:
SMRDATA:
.long 0x22011110 //BWSCON
.long 0x00000700 //BANKCON0
.long 0x00000700 //BANKCON1
.long 0x00000700 //BANKCON2
.long 0x00000700 //BANKCON3
.long 0x00000740 //BANKCON4
.long 0x00000700 //BANKCON5
.long 0x00018005 //BANKCON6
.long 0x00018005 //BANKCON7
.long 0x008C04F4 // REFRESH
.long 0x000000B1 //BANKSIZE
.long 0x00000030 //MRSRB6
.long 0x00000030 //MRSRB7
(2) 取消board_early_init_f里对MPLL的设置,修改 board/samsung/jz2440/jz2440.c中board_early_init_f函数代码如下:
int board_early_init_f(void)
{
struct s3c24x0_clock_power * const clk_power =
s3c24x0_get_base_clock_power();
struct s3c24x0_gpio * const gpio = s3c24x0_get_base_gpio();
#if 0
/* to reduce PLL lock time, adjust the LOCKTIME register */
writel(0xFFFFFF, &clk_power->locktime);
/* configure MPLL */
writel((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV,
&clk_power->mpllcon);
/* some delay between MPLL and UPLL */
pll_delay(4000);
#endif
/* configure UPLL */
writel((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV,
&clk_power->upllcon);
/* some delay between MPLL and UPLL */
pll_delay(8000);
/* set up the I/O ports */
writel(0x007FFFFF, &gpio->gpacon);
writel(0x00044555, &gpio->gpbcon);
writel(0x000007FF, &gpio->gpbup);
writel(0xAAAAAAAA, &gpio->gpccon);
writel(0x0000FFFF, &gpio->gpcup);
writel(0xAAAAAAAA, &gpio->gpdcon);
writel(0x0000FFFF, &gpio->gpdup);
writel(0xAAAAAAAA, &gpio->gpecon);
writel(0x0000FFFF, &gpio->gpeup);
writel(0x000055AA, &gpio->gpfcon);
writel(0x000000FF, &gpio->gpfup);
writel(0xFF95FFBA, &gpio->gpgcon);
writel(0x0000FFFF, &gpio->gpgup);
writel(0x002AFAAA, &gpio->gphcon);
writel(0x000007FF, &gpio->gphup);
return 0;
}
(3) 做了以上修改后,重新make编译成功,烧写u-boot.bin到Nor Flash,重启开发板,串口输出如下:
从上图可知,串口输出的是乱码,究其原因应该是波特率设置的问题。
3.3 查看波特率的设置,解决乱码的问题
(1) 查看串口波特率的设置,在common/board_f.c有函数board_init_f,它通过数组init_sequence_f里的函数初始化硬件,board_init_f函数的代码如下:
void board_init_f(ulong boot_flags)
{
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
/*
* For some archtectures, global data is initialized and used before
* calling this function. The data should be preserved. For others,
* CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
* here to host global data until relocation.
*/
gd_t data;
gd = &data;
/*
* Clear global data before it is accessed at debug print
* in initcall_run_list. Otherwise the debug print probably
* get the wrong vaule of gd->have_console.
*/
zero_global_data();
#endif
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}
source insight 里跳转找到这个数据,从第二节启动过程分析,去掉无关代码后的init_sequence_f数组如下:
static init_fnc_t init_sequence_f[] = {
/* setup_mon_len函数是设置gd结构体成员gd->mon_len的函数;
*在setup_mon_len函数中:gd->mon_len = (ulong)&__bss_end - (ulong)_start;
*gd->mon_len等于uboot.bin大小加上bss段的大小,_start为0
*从反汇编的setup_mon_len函数可知:(ulong)&__bss_end = 0x000c636c;
*所以,gd->mon_len = 0x000c636c;
*/
setup_mon_len,
/* 1.在initf_malloc函数里,由于CONFIG_SYS_MALLOC_F_LEN没定义,
* 直接返回0,相当于一个空函数
* 2.initf_console_record函数,同理
*/
initf_malloc,
initf_console_record,/* 空函数 */
arch_cpu_init, /* 空函数 */ /* basic arch cpu dependent setup */
initf_dm, /* 空函数 */
arch_cpu_init_dm, /* 空函数 */
mark_bootstage, /* 标记名字 *//* need timer, go after init dm */
board_early_init_f, /* 设置系统时钟,设置各个GPIO引脚 */
timer_init, /* initialize timer */
env_init, /* 设置gd的成员,初始化环境变量 *//* initialize environment */
init_baud_rate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_options, /* 打印uboot版本等信息 *//* say that we are here */
display_text_info, /* 打印uboot代码信息 *//* show debugging info if required */
print_cpuinfo, /* 打印uboot时钟频率信息 *//* display cpu info (and speed) */
announce_dram_init, /* 打印“ DRAM: ” */
/* TODO: unify all these dram functions? */
dram_init, /* 设置gd->ram_size= 0x04000000(64MB) *//* configure available RAM banks */
setup_dest_addr, /* 将gd->relocaddr、gd->ram_top指向SDRAM最顶端 */
reserve_round_4k, /* gd->relocaddr 4KB对齐 */
reserve_mmu, /* 预留16KB的MMU页表并且64KB对齐 */
reserve_trace, /* 空函数 */
/*reserve_uboot的作用是在SDRAM预留存放u-boot的空间(加上bss段)
*gd->relocaddr -= gd->mon_len;
*gd->relocaddr &= ~(4096 - 1);
* gd->start_addr_sp = gd->relocaddr;
*/
reserve_uboot,
/* reserve_malloc函数:
* gd->start_addr_sp = gd->start_addr_sp - TOTAL_MALLOC_LEN;
* 因为jz2440.h默认定义了CONFIG_ENV_ADDR,所以此时在include/common.h中
* 执行#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
* 也就是TOTAL_MALLOC_LEN=4*1024*1024+0x10000=4MB+64KB
* 预留4MB+64KB MALLOC内存池
*/
reserve_malloc,
/* reserve_board函数:
* gd->start_addr_sp -= sizeof(bd_t); 预留bd_t结构体空间,查看反汇编可知为80字节
* gd->bd = (bd_t *)gd->start_addr_sp; 指定重定位bd地址
* memset(gd->bd, '\0', sizeof(bd_t)); 清零
*/
reserve_board,
/*setup_machine函数:
*gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux
*/
setup_machine,
reserve_global_data,/* 预留gd结构体空间,查看反汇编可知为168字节。并设置gd->new_gd */
reserve_fdt, /* 如果设置了gd->new_fdt则预留fdt设备树空间,这里没有设置,不用管 */
reserve_arch,/* 空函数 */
/* reserve_stacks函数:
* gd->start_addr_sp -= 16;
* gd->start_addr_sp &= ~0xf;
* return arch_reserve_stacks();这里调用的不是board_f.c里的arch_reserve_stacks函数
* 因为该函数被__weak修饰符声明,调用的是arch/arm/lib/stack.c里的arch_reserve_stacks函数
* gd->irq_sp = gd->start_addr_sp;
* gd->start_addr_sp -= 16;
*/
reserve_stacks,
setup_dram_config,/* 设置gd结构体的SDRAM地址与大小 */
show_dram_config,/* 打印SDRAM信息 */
display_new_sp, /* 打印新的栈地址 */
reloc_fdt, /*没有设置设备树,忽略*/
/*setup_reloc函数:
*gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;计算重定位地址与链接地址偏移值,CONFIG_SYS_TEXT_BASE在jz2440.h定义为0,gd->reloc_off = gd->relocaddr
*memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));
*把旧的gd复制到新的gd地址里
*/
setup_reloc,
NULL,
};
(2) 从上面的init_sequence_f数组,可以找到串口初始化相关的函数serial_init,在drivers/serial/serial.c找到该函数的代码如下:
int serial_init(void)
{
gd->flags |= GD_FLG_SERIAL_READY;
return get_current()->start();
}
同样在drivers/serial/serial.c可以找到get_current函数,如下所示:
static struct serial_device *get_current(void)
{
struct serial_device *dev;
if (!(gd->flags & GD_FLG_RELOC))
dev = default_serial_console();
else if (!serial_current)
dev = default_serial_console();
else
dev = serial_current;
/* We must have a console device */
if (!dev) {
#ifdef CONFIG_SPL_BUILD
puts("Cannot find console\n");
hang();
#else
panic("Cannot find console\n");
#endif
}
return dev;
}
get_current函数可知,它返回的是一个serial_device 结构体,该结构如下:
struct serial_device {
/* enough bytes to match alignment of following func pointer */
char name[16];
int (*start)(void);
int (*stop)(void);
void (*setbrg)(void);
int (*getc)(void);
int (*tstc)(void);
void (*putc)(const char c);
void (*puts)(const char *s);
#if CONFIG_POST & CONFIG_SYS_POST_UART
void (*loop)(int);
#endif
struct serial_device *next;
};
所以,serial_init函数是通过调用serial_device结构体的start成员函数来初始化串口的。
(3) 从第二节的uboot启动过程分析可以知道,gd->flags是uboot是否重定位的标记,在执行 board_init_f函数时,代码还没开始重定位 ,此时gd->flags=0,所以执行的是if (!(gd->flags & GD_FLG_RELOC))
分支里的dev = default_serial_console();
。
在drivers/serial/serial_s3c24x0.c可以找到default_serial_console函数,代码如下:
__weak struct serial_device *default_serial_console(void)
{
#if defined(CONFIG_SERIAL1)
return &s3c24xx_serial0_device;
#elif defined(CONFIG_SERIAL2)
return &s3c24xx_serial1_device;
#elif defined(CONFIG_SERIAL3)
return &s3c24xx_serial2_device;
#else
#error "CONFIG_SERIAL? missing."
#endif
}
由于CONFIG_SERIAL1在jz2440.h文件被定义,所以该函数返回的是s3c24xx_serial0_device结构体,搜索s3c24xx_serial0_device可以在drivers/serial/serial_s3c24x0.c发现s3c24xx_serial0_device =INIT_S3C_SERIAL_STRUCTURE(0, "s3ser0");
,如下图所示:


原来它是一个宏, 以上的“##”连接符在编译时被去掉,连接符内的变量被替代,最终s3c24xx_serial0_device被定义成如下:
s3c24xx_serial0_device = {
\
.name = "s3ser0", \
.start = s3serial0_init, \
.stop = NULL, \
.setbrg = s3serial0_setbrg, \
.getc = s3serial0_getc, \
.tstc = s3serial0_tstc, \
.putc = s3serial0_putc, \
.puts = s3serial0_puts, \
}
对于函数声明的宏DECLARE_S3C_SERIAL_FUNCTIONS(0);
,展开后代码如下:
int s3serial0_init(void)
{
return serial_init_dev(0);
}
void s3serial0_setbrg(void)
{
serial_setbrg_dev(0);
}
int s3serial0_getc(void)
{
return serial_getc_dev(0);
}
int s3serial0_tstc(void)
{
return serial_tstc_dev(0);
}
void s3serial0_putc(const char c)
{
serial_putc_dev(0, c);
}
void s3serial0_puts(const char *s)
{
serial_puts_dev(0, s);
}
由此可见,宏DECLARE_S3C_SERIAL_FUNCTIONS(0);
一下子就声明了多个函数,现在最关心的是s3serial0_init
函数,它调用serial_init_dev
函数,该函数的代码如下:
/* Initialise the serial port. The settings are always 8 data bits, no parity,
* 1 stop bit, no start bits.
*/
static int serial_init_dev(const int dev_index)
{
/* 得到ULCON0控制寄存器基地址0x50000000 */
struct s3c24x0_uart *uart = s3c24x0_get_base_uart(dev_index);
/* FIFO enable, Tx/Rx FIFO clear */
writel(0x07, &uart->ufcon);
writel(0x0, &uart->umcon);
/* Normal,No parity,1 stop,8 bit */
writel(0x3, &uart->ulcon);
/*
* tx=level,rx=edge,disable timeout int.,enable rx error int.,
* normal,interrupt or polling
*/
writel(0x245, &uart->ucon);
_serial_setbrg(dev_index); /*设置波特率*/
return (0);
}
串口输出乱码,很大原因是波特率设置不正确,所以我们需要了解 _serial_setbrg函数是如何设置波特率的,_serial_setbrg函数的代码如下:
static void _serial_setbrg(const int dev_index)
{
struct s3c24x0_uart *uart = s3c24x0_get_base_uart(dev_index);
unsigned int reg = 0;
int i;
/* value is calculated so : (int)(PCLK/16./baudrate) -1 */
reg = get_PCLK() / (16 * gd->baudrate) - 1;
writel(reg, &uart->ubrdiv);
for (i = 0; i < 100; i++)
/* Delay */ ;
}
在这里终于看到有时钟相关的函数:get_PCLK()
,然后跳转到 arch/arm/cpu/arm920t/s3c24x0/speed.c 文件的get_PCLK()
,代码如下:
/* return PCLK frequency */
ulong get_PCLK(void)
{
const uint8_t pclk_divisors[] = {
1, 2, 4, 8 };
struct syscon_regs *syscon = (struct syscon_regs *)SYSCON_BASE;
const uint32_t clkset1 = readl(&syscon->clkset1);
const uint8_t pclk_div =
pclk_divisors[(clkset1 >> SYSCON_CLKSET1_PCLK_DIV_SHIFT) & 3];
const ulong pclk_rate = get_HCLK() / pclk_div;
return pclk_rate;
}
从上面的代码看,PCLK通过HCLK频得到的,那么继续看get_HCLK
函数,代码如下图所示:
从上图中可以发现#ifdef CONFIG_S3C2440
这一句是黑色的,说明没有定义这个CONFIG_S3C2440
;
处理措施:在include/configs/jz2440.h中去掉CONFIG_S3C2410 ,换成CONFIG_S3C2440,如下所示:
然后,执行如下命令重新编译:
make distclean
make jz2440_defconfig
make
编译成功,重新烧写到开发的Nor Flash,重启开发板,串口输出信息如下图所示:
从上图可知,串口终于可以正常输出了,但是Flash和NAND都是显示0,接下来会继续修改代码使uboot支持Flash和NAND,这一节就到此结束。