Renesas E2 平台启动时间调试

1、问题描述

仪表项目DM001,使用的是RenesasR-Car E2(R8A7740)平台,使用U-boot启动,Start Kernel之前的时间比较长,约有8~9秒。超出了系统要求,目标是把时间缩小到1秒以内。

此SoC的启动时,内置的ROM代码会从QSPI接口的Flash中读取前16K数据到0xe6300000地址,从这里开始运行,由于只读取16K数据,所以无法从U-Boot(180K左右)直接启动,解决方法是自己编写了一小段代码,叫做Miniboot,此代码负责把U-Boot加载到内存在,然后再跳到U-Boot中运行。

启动过程大致如下:

上电àROM运行à加载QSPI接口Flash 16K代码执行à从Flash加载U-Boot运行à从eMMC加载Kernel+Ramdiskà运行Kernel。

eMMC采用的是MicronMTFC8GLWDM-3M AIT Z,8G的eMMC,最高时钟52MHz。

Flash采用的是SpansionS25FL164K,8M的SPI接口Flash。

原始的启动Log大致如下:

Miniboot Version --0.1

Load uboot 230KB from spi offset 0x20000 to0xe6304000

Read uboot ok

[   0.003]

[   0.007]

[   0.011] U-Boot 2013.01.01 (May 12 2016 - 11:15:54)

[   0.027]

[   0.032] CPU: Renesas Electronics R8A7794 rev 2.0

[   0.048] Board: Paris3E2 Board

[   0.058]

[   0.062] DRAM:  1 GiB

[   0.105] MMC:   sh_mmcif: 0,sh-sdhi: 1

[   0.117] Using default environment

[   0.129]

[   0.134] In:    serial

[   0.142] Out:   serial

[   0.150] Err:   serial

[   0.158] Hit any key to stop autoboot: 1  0

[   1.733] mmc0(part 0) is current device

[   1.746]

[   1.750] MMC read_dma: dev # 0, block # 2048, count 6999 ... 6999 blocksread_dma: OK

[   4.113]

[    4.117] MMC read_dma: dev# 0, block # 22528, count 15758 ... 15758 blocks read_dma: OK

[   9.902] ## Booting kernel from Legacy Image at 40007fc0 ...

[   9.921]    Image Name:   paris3-e2 20160421-205019

[   9.938]    Image Type:   ARM Linux Kernel Image (uncompressed)

[   9.958]    Data Size:    3583299 Bytes = 3.4 MiB

[   9.974]    Load Address: 40007fc0

[   9.985]    Entry Point: 40008000

[   9.997]    Verifying Checksum ...OK

[  10.103] ## Loading init Ramdisk from Legacy Image at 40f00000 ...

[  10.124]    Image Name:   paris3_e2 initramfs

[  10.138]    Image Type:   ARM Linux RAMDisk Image (gzip compressed)

[  10.160]    Data Size:    8067674 Bytes = 7.7 MiB

[  10.175]    Load Address: 00000000

[  10.187]    Entry Point:  00000000

[  10.198]    Verifying Checksum ...OK

[  10.422]    XIP Kernel Image ... OK

[  10.442] OK

[  10.446]

[  10.450] Starting kernel ...

[  10.460]

从Log上看,从eMMC读取Kernel和Ramdisk的时间很长,11MB的Kernel+Ramdisk大概花了6秒时间才加载成功,平均每秒只能加载2MB左右的数据,这完全不符合eMMC的应有速度。所以决定先从eMMC读取入手。

2、eMMC读取优化

eMMC的接口如下:

看了一下代码,读取已经采用多块连续读的方式,也就是说命令只发送一次,然后直到数据读取完成,用示波器测量了Clock信号,发现系统启动时,eMMC的Clock居然只有6MHz,而不是正常的48MHz,查代码,eMMC相关寄存器中已经设置为48MHz了,但输出只有6MHz,查Renesas数据手册,找到Clock Pulse Generator(CPG)中有一个MMC0 Clock Frequency Control Register(MMC0CKCR)寄存器负责分频,没有进行显式的设置,读出原始值,发现为0x0F。

根据手册说明,实际Clock为MMC原始频率/(15+1)

原始频率为100M左右,100/16=6MHz。

根据手册,最多可支持到52Mhz,所以设置为7比较合适。修改board/renesas/Paris3E2.c文件中的board_init()函数,增加以下内容:

/* MMC Clock Config*/

#define CPG_BASE0xE6150000

#defineCONFIG_SH_MMC0CK       (CPG_BASE+0x240)

#defineMMC0CK_MASK            (1U<<8)

#defineMMCCLK_RATIO   (1U) | (1U<<1) |(1U<<2)

/* Note: system clockabout 384Mz, here we set the ratio to 7, the MMC clock = 384/(7+1) = 48Mhz

The maximum MMC clock is 52Mhz

*/

val = readl(CONFIG_SH_MMC0CK);

debug("mmcclk current value0x%X\n", val);

val &= MMC0CK_MASK;

val |= MMCCLK_RATIO;

writel(val, CONFIG_SH_MMC0CK);

系统中MMC Clock:

MMC0CKCR寄存器说明:

修改后,再测量,Clock为48Mhz,读取时间大大减少。总体时间缩短到3秒左右。

3、U-Boot运行速度调整

在查看U-Boot的配置文件时include/configs/Paris3E2.h,发现有一个值

#define        CONFIG_SYS_CLK_FREQ     10000000

根据Renesas 手册,CPU应该是1GHz的,怎么会只有10MHz,试着把它调到1GHz,发现打印出来的时间大大缩小。

[   0.000]

[   0.000]

[   0.000] U-Boot 2013.01.01 (May 13 2016 - 12:57:46)

[   0.000]

[   0.000] CPU: Renesas Electronics R8A7794 rev 2.0

[   0.000] Board: Paris3E2 Board

[   0.000]

[   0.000] DRAM:  1 GiB

[   0.001] MMC:   sh_mmcif: 0,sh-sdhi: 1

[   0.001] Using default environment

[   0.001]

[   0.001] In:    serial

[   0.001] Out:   serial

[   0.001] Err:   serial

[   0.001] Hit any key to stop autoboot: 0

[   0.012] mmc0(part 0) is current device

[   0.012]

[   0.012] MMC read_dma: dev # 0, block # 2048, count 6999 ... 6999 blocksread_dma: OK

[   0.015]

[    0.016] MMC read_dma: dev# 0, block # 22528, count 15758 ... 15758 blocks read_dma: OK

[   0.023] ## Booting kernel from Legacy Image at 40007fc0 ...

[   0.024]    Image Name:   paris3-e2 20160421-205019

[   0.024]    Image Type:   ARM Linux Kernel Image (uncompressed)

[   0.024]    Data Size:    3583299 Bytes = 3.4 MiB

[   0.024]    Load Address: 40007fc0

[   0.024]    Entry Point:  40008000

[   0.024]    Verifying Checksum ...OK

[   0.025] ## Loading init Ramdisk from Legacy Image at 40f00000 ...

[   0.026]    Image Name:   paris3_e2 initramfs

[   0.026]    Image Type:   ARM Linux RAMDisk Image (gzip compressed)

[    0.026]   Data Size:    8067674 Bytes = 7.7MiB

[   0.026]    Load Address: 00000000

[   0.026]    Entry Point:  00000000

[   0.026]    Verifying Checksum ...OK

[   0.029]    XIP Kernel Image ... OK

[   0.029] OK

[   0.029]

[   0.029] Starting kernel ...

实际时间没有这么小,但基本上在2秒左右就可以了。

经过查看代码,系统CPU的Clock并不是通过此值设置,而且目前的CPU时间应该是正常的。此值只会影响到Timer,间接的影响到udelay()/mdelay函数的实际等待时间,提高后实际上会大大减少等待。

直接改大后发现有副作用,就是在U-Boot中输入run bootcmd_mfg进行升级时,扫描U盘的时间大大加长。另一个问题是如果把等待按键的时间设置为非零时,等待的时间加长了几十倍。

#defineCONFIG_BOOTDELAY  1

对这个问题没有深入去研究,只是通过在相应处理之前把此值设置回来,并重启定时器可解决,相关代码如下:

定义一个高频及和原始频率之间的比值 include/configs/Paris3E2.h

#define        CONFIG_SYS_CLK_FREQ     10000000

#define       CONFIG_SYS_CLK_FREQ_HIGH       1000000000

 

#ifdef CONFIG_SYS_CLK_FREQ_HIGH

#define CONFIG_SYS_CLK_FREQ_DIV       (CONFIG_SYS_CLK_FREQ_HIGH/CONFIG_SYS_CLK_FREQ)

#endif /* CONFIG_SYS_CLK_FREQ_HIGH */

include/sh_tmu.h  定时器相关代码get_tmu0_clk_rate()改为

static inlineunsigned long get_tmu0_clk_rate(void)

{

#ifdef CONFIG_SYS_CLK_FREQ_HIGH

       extern intg_sys_high_speed;

       returng_sys_high_speed?CONFIG_SYS_CLK_FREQ_HIGH:CONFIG_SYS_CLK_FREQ;

#else

return CONFIG_SYS_CLK_FREQ;

#endif /* CONFIG_SYS_CLK_FREQ_HIGH */

}

通过一个全局变量g_sys_high_speed来决定当前返回高频还是低频。此值初始值设置为1。在文件common/main.c

#ifdef CONFIG_SYS_CLK_FREQ_HIGH

int g_sys_high_speed = 1;

#endif /* CONFIG_SYS_CLK_FREQ_HIGH */

在等待按键函数abortboot()中,修改delay部分。把延迟的时间除以之前我们定义的高低频的比率。修改后,等待按键的时间正常。

       #ifdefCONFIG_SYS_CLK_FREQ_HIGH

                      udelay(10000/CONFIG_SYS_CLK_FREQ_DIV); /* Fix when high CPU freq, itwill delay very long time... */

       #else

udelay(10000);

       #endif /*CONFIG_SYS_CLK_FREQ_HIGH */

在main_loop()函数中,在读到按键后,把频率改回原值,增加以下代码:

#ifdef CONFIG_SYS_CLK_FREQ_HIGH

               g_sys_high_speed = 0; /* Set CPUclock to low, otherwise the USB enum will very slow. */

              reset_timer();

#endif /* CONFIG_SYS_CLK_FREQ_HIGH */

后记:关于此值的作用,个人理解就是影响定时器的实际定时,通过用示波器测量,发现原来设置为10MHz时,本身也不正确,1秒钟延时实际上只有300ms左右。也就是只有1/3左右。后来从Renesas拿到一个补丁,把此值修改为32.5M,正好和之前的测试值吻合。不过由于和1G相比,32.5M还是明显会慢一些,所以还是继续保留了之前的代码。

另外,从另一个项目M2中,移植了eMMC的DMA部分代码,把读取DMA的方式改为DMA方式。修改lib/iauto_bootcmd.c

#ifdef CONFIG_IAUTO_MMC_DMA

#define IAUTO_ENV_BOOTCMD_NORMAL               "run"IAUTO_EVN_VAR_BOOTARG_IAUTO";"\

STRMMC_DEV(IAUTO_CFG_MMC_DEV_NUM)";"\

"mmc
read_dma ${"IAUTO_ENV_VAR_KERLOADADDR"}${"IAUTO_ENV_VAR_KERLOCATION"}${"IAUTO_ENV_VAR_KERLOADSIZE"};"\

"mmc
read_dma${"IAUTO_ENV_VAR_RFSLOADADDR"}${"IAUTO_ENV_VAR_RFSLOCATION"}${"IAUTO_ENV_VAR_RFSLOADSIZE"};"\

"bootm   ${"IAUTO_ENV_VAR_KERLOADADDR"}${"IAUTO_ENV_VAR_RFSLOADADDR"} \0"

#else

#define IAUTO_ENV_BOOTCMD_NORMAL               "run"IAUTO_EVN_VAR_BOOTARG_IAUTO";"\xx

STRMMC_DEV(IAUTO_CFG_MMC_DEV_NUM)";"\

"mmc read${"IAUTO_ENV_VAR_KERLOADADDR"}${"IAUTO_ENV_VAR_KERLOCATION"}${"IAUTO_ENV_VAR_KERLOADSIZE"};"\

"mmc read ${"IAUTO_ENV_VAR_RFSLOADADDR"}${"IAUTO_ENV_VAR_RFSLOCATION"}${"IAUTO_ENV_VAR_RFSLOADSIZE"};"\

"bootm   ${"IAUTO_ENV_VAR_KERLOADADDR"}${"IAUTO_ENV_VAR_RFSLOADADDR"} \0"

#endif /* CONFIG_IAUTO_MMC_DMA */

注:DMA方式不一定会提升速度,只是可以释放出CPU的时间来处理其它一些事情。实际测试中也是如此,在没有修改eMMC时钟之前,打开DMA操作还增加了近1秒的时间。

另外,为了方便调试,增加了一个GPIO来方便通过示波器测量实际的时间。

include/configs/Paris3E2.h增加定义 GPIO_4_13

#define CONFIG_SYS_BOOT_TP_GPIO        GPIO_GP_4_13

/* GP_4_13 used for boot stage indicate.

miniboot H --> L

uboot, boardinit(H) --> mainloog(L) --> startkernel(H)

*/

Board/renesas/Paris3E2/Paris3E2.c增加函数

#ifdef CONFIG_SYS_BOOT_TP_GPIO

void boot_tp_gpio_ind(int onoff)

{

gpio_request(CONFIG_SYS_BOOT_TP_GPIO, "boot_ind");

gpio_direction_output(CONFIG_SYS_BOOT_TP_GPIO, onoff);

}

#endif /* CONFIG_SYS_BOOT_TP_GPIO */

在需要的地方,只需要调用boot_tp_gpio_ind()就可以设置GPIO高电平或低电平了。

用示波器测试了一下启动的时间,第一次高电平是Miniboot启动,第二次高电平是U-boot启动时设置的,第二次的低电平是Start kernel调用之前设置的。

从图上看,整个Bootloader启动花了1.88秒时间,从Miniboot到U-Boot启动消耗了1秒时间。

4、Miniboot读Flash优化

从上面的图中可以看出,Miniboot花了1秒钟的时间,显然比较长。由于Miniboot代码比较少,大概看了一下,主要的工作就是通过QSPI接口从Flash中读取230K固定大小的U-Boot代码到内存中,然后就转到U-Boot中执行。

上图是Flash的接口电路。此Flash可以支持四数据线的QSPI模式,和普通的SPI相比,数据线有四根,速度上有优势。

根据之前调试eMMC的经验,上来先测量Clock,发现Clock是48.75MHz,应该是比较高了,但发现一个现象,Clock是非连续的,每两个Clock之后就很长的空闲时间,加上空闲时间后的周期大约是160K左右。读取的数据大约是320KB左右。符合现在差不多需要1秒的情况。看来就是因为数据慢引起的。

查阅Flash的数据手册,读取可支持Single/Dual/Quad几种模式,分别使用单根、两根及四根数据线,目前代码中已经采用了最快的Quad模式。看来已经是最优了。

还是决定从Clock入手。Quad模式最高可支持到108Mhz,目前是48M,显然还有空间。查Renesas手册,QSPI最高可到97Mhz.

配置Bit RateRegister(SPBR),目前配置为48M,只需要把SPBR(7:0)改为0即可把频率提高到97M。改了一下,Clock速度的确提高了,但整体时间减少不明显。

又回到之前发现的Clock不连续的问题,两个Clock看来是输出了一个BYTE,因为四线,一个BYTE需要读两次,所以是连续两个BYTE,那如果一次输出32bit,应该是8个Clock,按这个思路改了一下代码,果然出8个连续的Clock,虽然加上空闲的周期还是169K左右,但整体读取速度提高了4倍。

修改miniboot/common/spiflash.c文件

sh_qspi_recv()函数改为一次读4字节。

int sh_qspi_recv(unsigned char *tdata,unsigned long flags)

{

while(!(readb(SH_QSPI_SPSR) & SH_QSPI_SPSR_SPRFF)) {

udelay(10);

}

#if defined(SPI_READ_QUADBYTES)

return readl(SH_QSPI_SPDR);

#else

*tdata = readb(SH_QSPI_SPDR);

#endif

return0;

}

sh_qspi_xfer_quad()改为

int sh_qspi_xfer_quad(

unsignedint cbyte, const void *cmd,

unsignedint dbyte, const void *dout, void *din, unsigned long flags)

{

unsignedchar *tdata, ddata = 0;

intret = 0;

writeb(0x08,SH_QSPI_SPCR);

writew(0xe083,SH_QSPI_SPCMD0);

writew(0xe083,SH_QSPI_SPCMD1);

if(dout != NULL)

writew(0xe043,SH_QSPI_SPCMD2);

else{

#ifdef SPI_READ_QUADBYTES

writew(0x0251, SH_QSPI_SPCMD2);

#else

writew(0xe051,SH_QSPI_SPCMD2);

#endif

}

writeb(0xc0,SH_QSPI_SPBFCR);

writeb(0x00,SH_QSPI_SPBFCR);

writeb(0x02,SH_QSPI_SPSCR);

writel(1,SH_QSPI_SPBMUL0);

writel(cbyte- 1, SH_QSPI_SPBMUL1);

#ifdef SPI_READ_QUADBYTES

writel((dbyte+3)/4, SH_QSPI_SPBMUL2);

#else

writel(dbyte,SH_QSPI_SPBMUL2);

#endif /* SPI_READ_QUADBYTES */

writeb(0x48,SH_QSPI_SPCR);

/*command transfer */

if(cmd != NULL)

tdata= (unsigned char *)cmd;

else

return1;

while(cbyte > 0) {

ret= sh_qspi_xfer(tdata, &ddata);

if(ret)

break;

tdata++;

cbyte--;

}

/*data transfer */

if(dout != NULL && din != NULL)

print("sh_qspi_xfer_qreadfull duplex is no supported\n");

if(dout != NULL) {

tdata= (unsigned char *)dout;

while(dbyte > 0) {

ret= sh_qspi_send(tdata, flags);

if(ret)

break;

tdata++;

dbyte--;

}

while((readw(SH_QSPI_SPBDCR) & 0x3f00)) {

udelay(10);

}

}else if (din != NULL) {

tdata= (unsigned char *)din;

while(dbyte > 0) {

ret= sh_qspi_recv(tdata, flags);

#ifdefined(SPI_READ_QUADBYTES)

*tdata++ = (unsignedchar)((ret>>24)&0xFF);

*tdata++ = (unsignedchar)((ret>>16)&0xFF);

*tdata++ = (unsignedchar)((ret>>8)&0xFF);

*tdata++ = (unsignedchar)((ret)&0xFF);

dbyte-=4;

#else

if(ret)

break;

tdata++;

dbyte--;

#endif

}

}

returnret;

}

相关的寄存器,CommandResigter (SPCMDn),把数据的长度改为32bits,同时把等待的周期都改为零。

另个,由于一次四字节,所以数据长度寄存器Transfer Data Length Multiplier Setting Register(SPBMULn)需要改为原长度/4,考虑到存在非正好四字节的问题,所以把长度+3/4。

目前的设置中,并没有把控制器中的Buffer打开,也就是说每一个字节就触发读取一次,显示比较慢。参考Buffer Control Register (SPBFCR),把Receive Buffer设置为最大。

只改RXTRG值,发现程序不工作了,应该是SPI不会触发数据满中断了(不明白为什么),原代码中是通过Status Register (SPSR)中的Receive Buffer Full Flag (SPRFF)位来判定的。现在看来改用Buffer之后就不会触发这一位了(这大概就是之前代码没有打开Buffer的原因吧),看了一下数据手册,还有一个寄存器Buffer Data Count Register(SPBDCR)指示Buffer中数据有多少个。只要此寄存器中相关位非零就表示在Buffer中还有未读取的数据。

相关寄存器说明见下:

SPSR说明:

SPBDCR说明:

代码修改:miniboot/common/spiflash.c,sh_qspi_xfer()函数在发送命令等待命令结果的判断也需要改为通过SPBDCR判断。

intsh_qspi_xfer(u8 *tdata, u8 *rdata)

{

while (!(readb(SH_QSPI_SPSR) &SH_QSPI_SPSR_SPTEF)) {

udelay(10);

}

writeb(*tdata, SH_QSPI_SPDR);

#ifdefined(SPI_USE_RXBUFFER)

while(!(readw(SH_QSPI_SPBDCR)&SH_QSPI_SPBDCR_RXBC) ) {

#else

while (!(readb(SH_QSPI_SPSR) &SH_QSPI_SPSR_SPRFF)) {

#endif /*SPI_USE_RXBUFFER */

udelay(10);

}

*rdata = readb(SH_QSPI_SPDR);

return 0;

}

原接收数据的函数sh_qspi_recv()改为通过SPBDCR判断是否有数据,并把返回结果改为Buffer未读取的数据。

intsh_qspi_recv(unsigned char *tdata, unsigned long flags)

{

#ifndefSPI_USE_RXBUFFER

while (!(readb(SH_QSPI_SPSR) &SH_QSPI_SPSR_SPRFF)) {

udelay(10);

}

#endif/* SPI_USE_RXBUFFER */

#ifdefined(SPI_USE_RXBUFFER)

while(!(readw(SH_QSPI_SPBDCR)&SH_QSPI_SPBDCR_RXBC) ) {

//udelay(10);

}

return readw(SH_QSPI_SPBDCR)&SH_QSPI_SPBDCR_RXBC;

#elifdefined(SPI_READ_QUADBYTES)

return readl(SH_QSPI_SPDR);

#else

*tdata = readb(SH_QSPI_SPDR);

#endif

return 0;

}

sh_qspi_xfer_quad()改为:

intsh_qspi_xfer_quad(

unsigned int cbyte, const void *cmd,

unsigned int dbyte, const void *dout,void *din, unsigned long flags)

{

unsigned char *tdata, ddata = 0;

int ret = 0;

#ifdefined(SPI_USE_RXBUFFER)

int   i;

#if defined(SPI_READ_QUADBYTES)

int val;

#endif /* SPI_READ_QUADBYTES */

#endif/* SPI_USE_RXBUFFER */

writeb(0x08, SH_QSPI_SPCR);

writew(0xe083, SH_QSPI_SPCMD0);

writew(0xe083, SH_QSPI_SPCMD1);

if (dout != NULL)

writew(0xe043, SH_QSPI_SPCMD2);

else {

#ifdefSPI_READ_QUADBYTES

writew(0x0251, SH_QSPI_SPCMD2);

#elif defined(SPI_USE_RXBUFFER)

writew(0x0051, SH_QSPI_SPCMD2);

#else

writew(0xe051, SH_QSPI_SPCMD2);

#endif

}

writeb(0xc0, SH_QSPI_SPBFCR);

#ifdefined(SPI_USE_RXBUFFER)

writeb(0x07, SH_QSPI_SPBFCR);

#else

writeb(0x00, SH_QSPI_SPBFCR);

#endif /* SPI_USE_RXBUFFER*/

writeb(0x02, SH_QSPI_SPSCR);

writel(1, SH_QSPI_SPBMUL0);

writel(cbyte - 1, SH_QSPI_SPBMUL1);

#ifdefSPI_READ_QUADBYTES

writel((dbyte+3)/4, SH_QSPI_SPBMUL2);

#else

writel(dbyte, SH_QSPI_SPBMUL2);

#endif /*SPI_READ_QUADBYTES */

writeb(0x48, SH_QSPI_SPCR);

/* command transfer */

if (cmd != NULL)

tdata = (unsigned char *)cmd;

else

return 1;

while (cbyte > 0) {

ret = sh_qspi_xfer(tdata,&ddata);

if (ret)

break;

tdata++;

cbyte--;

}

/* data transfer */

if (dout != NULL && din != NULL)

print("sh_qspi_xfer_qreadfull duplex is no supported\n");

if (dout != NULL) {

tdata = (unsigned char *)dout;

while (dbyte > 0) {

ret = sh_qspi_send(tdata,flags);

if (ret)

break;

tdata++;

dbyte--;

}

while ((readw(SH_QSPI_SPBDCR)& 0x3f00)) {

udelay(10);

}

} else if (din != NULL) {

tdata = (unsigned char *)din;

while (dbyte > 0) {

ret = sh_qspi_recv(tdata,flags);

#if defined(SPI_USE_RXBUFFER)

#if defined(SPI_READ_QUADBYTES)

for ( i = 0; i < ret; i++ ) {

val =readl(SH_QSPI_SPDR);

*tdata++ = (unsignedchar)((val>>24)&0xFF);

*tdata++ = (unsignedchar)((val>>16)&0xFF);

*tdata++ = (unsignedchar)((val>>8)&0xFF);

*tdata++ = (unsignedchar)((val)&0xFF);

}

dbyte-=(ret*4);

#else

for ( i = 0; i < ret; i++ ) {

*tdata++ =readb(SH_QSPI_SPDR);

}

dbyte-=ret;

#endif /* SPI_READ_QUADBYTES */

#elif defined(SPI_READ_QUADBYTES)

*tdata++ = (unsignedchar)((ret>>24)&0xFF);

*tdata++ = (unsignedchar)((ret>>16)&0xFF);

*tdata++ = (unsignedchar)((ret>>8)&0xFF);

*tdata++ = (unsignedchar)((ret)&0xFF);

dbyte-=4;

#else

if (ret)

break;

tdata++;

dbyte--;

#endif

}

}

#ifdefined(SPI_USE_RXBUFFER)

writeb(0xc0, SH_QSPI_SPBFCR);

writeb(0x00, SH_QSPI_SPBFCR);

#endif /*SPI_USE_RXBUFFER */

if (flags & SPI_XFER_END)

writeb(0x08, SH_QSPI_SPCR);

return ret;

}

这两步完成之后,再测试了一下,Miniboot的时间减少到300ms左右了。只有之前的1/3了。再把Clock提高到97Mhz,又可节省约几十ms。

小插曲:如此改动之后,发现了一个新问题,在U-Boot/Kernel中无法驱动/dev/mtd设备,也就是说QSPI设备没有驱动起来。当时发现只需要在跳转到U-Boot之前再执行一次spi_init()就可以了。但这在之后把SPI频率提高到97MHz之后,发生了两次导致Flash进入OTP模式,只可读不能写的问题,实际的原因至今不详,初步估计是由于在传输完数据之后,没有显式的向Flash发送结束命令。引起Flash一起处理接收状态,启动过程中数据线的上扰动可能导致了Flash进入OTP状态。在传输完成后,发送结束标志代码加上后,就不再需要多次调用spi_init(),也没有再发生Flash进入OTP的情况了。

OTP的状态是通过StatusRegister来判断的。出问题后,SRP0位及SRP1都是1,寄存器的说明见下(此为Flash中的寄存器)。

另外在spi_init()中设置Status Register的代码中增中了相关的保护。miniboot/common/sqiflash.c的spi_init()

voidspi_init(void)

{

writeb(0x8,     SH_QSPI_SPCR);         //set master mode,set interrupt mask

writeb(0x0,     SH_QSPI_SSLP);         //set SSL signal level

writeb(0x6,     SH_QSPI_SPPCR);              //set MOSI signal value when transfer is in idle state

writeb(0x1,     SH_QSPI_SPBR);  //set transfer bit rate SPBR[7:0]=1 SPCMD0:BRDV[1:0]=0QSPICLK=48.75Mbps

writeb(0x0,     SH_QSPI_SPDCR);

writeb(0x0,     SH_QSPI_SPCKD);             //set clock delay value

writeb(0x0,     SH_QSPI_SSLND);             //set SSL negation delay value

writeb(0x0,     SH_QSPI_SPND);        //set next-access delay value

writew(0xe083, SH_QSPI_SPCMD0); // transfer data length=8bit SPB[3:0]=0, spioperation mode single mode SPIMOD[1:0]=0

writeb(0xc0, SH_QSPI_SPBFCR);

writeb(0x00, SH_QSPI_SPBFCR);

writeb(0x00, SH_QSPI_SPSCR);

writeb(0x48, SH_QSPI_SPCR);

{

/* enable quad transfer */

u8 cr, sr, srcr[2];

u8 cmd = CMD_WRITE_STATUS;

spi_flash_cmd(CMD_READ_CONFIG,&cr, 1);

spi_flash_cmd(CMD_READ_STATUS,&sr, 1);

cr &= 0xC6; // Clear LB1~LB3,SRP1 bit

cr |= 0x02; // Set Quad Enable bit

sr &= 0xC3; // Clear TB, BP2~0bit

srcr[0] = sr;

srcr[1] = cr;

spi_flash_cmd(CMD_WRITE_ENABLE,NULL, 0);

spi_flash_cmd_write(&cmd, 1,srcr, 2);

if (spansion_wait_ready(10000))

print("qual enablespansion_wait_ready timeout\n");

}

{

u8 status1, status2;

spi_flash_cmd(CMD_READ_STATUS,&status1, sizeof(status1));

spi_flash_cmd(CMD_READ_CONFIG,&status2, sizeof(status2));

print("\n\nQSPI:status[1]=%x,status[2]=%x\n",status1,status2);

}

}

5、Miniboot读Flash进一步优化

从读取速度方面已经基本到头了,没什么优化的空间了。考虑到实际U-Boot大小只有180K左右,没必要读取230K,经过测试发现,如果减少50K,就可以节省约50ms,非常可观。由于事先Miniboot并不知道U-Boot有多大,所以只好读一个几乎是最大值的大小。

通过在 U-Boot的最前面增加几个字节,来指示实际U-Boot的大小,就可以减少读取的实际字节数,但这需要在编译完U-Boot之后,人为的增加一个文件头。

在前面的代码中,我们可以看到,读到32bit之后,经过测试默认字节序是反的,还需要通过移位把四个字节分别转到内存中,如果事先把U-Boot的Binary文件字节序就反转,直接四字节赋值就可以省掉这个过程。实际测试,发现在原基础上又可以节省20ms左右。

注:在ControlRegister (SPCR)中有关于反序的控制位,但好象只能用于DMA模式,非DMA模式下,设置了此位测试无效。

6、Miniboot关闭UART输出

通过直接关闭UART输出(移除UART驱动),大概可以节省90ms左右的时间。完成以上步骤后,Miniboot的时间约为140ms左右。

7、U-Boot继续优化

Miniboot时间已经压缩到140ms左右,现在总的时间,基本上1秒左右。重心重新移到U-Boot上来。

U-Boot是按顺序读取Kernel和Ramdisk的。先读的Kernel,再读Ramdisk,之后再分别解Kernel和Ramdisk,由于是DMA方式,所以实际上读取过程中,CPU一直处于等待传送结束标志。如果在读取Ramdisk过程中,先进行Kernel的解压工作,应该是可行的。

把common/cmd_bootm.c中的bootm_start()函数进行拆解,分成Top Half和Bottom Half两部分。参数部分改为写死而不是通过参数来传输,在实现上来说并不是很好,但在特定的例子下问题不大,因为这些值是不会变的。

#if defined(CONFIG_IAUTO_PRELOAD_KNL)

#define FLAGS      0

#define ARGC 3

const char*g_argv[]={

"bootm",

"0x40007FC0",

"0x40F00000"

};

static void       *gos_hdr;

static int  g_boot_running = 0;

intbootm_start_th(void)

{

void        *os_hdr;

if ( g_boot_running ) return 0;

//printf("bootm_start_th\n");

memset((void *)&images, 0,sizeof(images));

images.verify =getenv_yesno("verify");

boot_start_lmb(&images);

bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START,"bootm_start");

/* get kernel image header, start addressand length */

os_hdr = boot_get_kernel(NULL, FLAGS,ARGC, (char * const*)g_argv,

&images,&images.os.image_start, &images.os.image_len);

gos_hdr = os_hdr;

if (images.os.image_len == 0) {

puts("ERROR: can‘t get kernelimage!\n");

return 1;

}

/* get image parameters */

switch (genimg_get_format(os_hdr)) {

case IMAGE_FORMAT_LEGACY:

images.os.type =image_get_type(os_hdr);

images.os.comp =image_get_comp(os_hdr);

images.os.os =image_get_os(os_hdr);

images.os.end = image_get_image_end(os_hdr);

images.os.load =image_get_load(os_hdr);

break;

#ifdefined(CONFIG_FIT)

case IMAGE_FORMAT_FIT:

if(fit_image_get_type(images.fit_hdr_os,

images.fit_noffset_os,&images.os.type)) {

puts("Can‘t get imagetype!\n");

bootstage_error(BOOTSTAGE_ID_FIT_TYPE);

return 1;

}

if(fit_image_get_comp(images.fit_hdr_os,

images.fit_noffset_os,&images.os.comp)) {

puts("Can‘t get imagecompression!\n");

bootstage_error(BOOTSTAGE_ID_FIT_COMPRESSION);

return 1;

}

if(fit_image_get_os(images.fit_hdr_os,

images.fit_noffset_os,&images.os.os)) {

puts("Can‘t get imageOS!\n");

bootstage_error(BOOTSTAGE_ID_FIT_OS);

return 1;

}

images.os.end =fit_get_end(images.fit_hdr_os);

if (fit_image_get_load(images.fit_hdr_os,images.fit_noffset_os,

&images.os.load)){

puts("Can‘t get imageload address!\n");

bootstage_error(BOOTSTAGE_ID_FIT_LOADADDR);

return 1;

}

break;

#endif

default:

puts("ERROR: unknown imageformat type!\n");

return 1;

}

/* find kernel entry point */

if (images.legacy_hdr_valid) {

images.ep =image_get_ep(&images.legacy_hdr_os_copy);

#ifdefined(CONFIG_FIT)

} else if (images.fit_uname_os) {

ret =fit_image_get_entry(images.fit_hdr_os,

images.fit_noffset_os,&images.ep);

if (ret) {

puts("Can‘t get entrypoint property!\n");

return 1;

}

#endif

} else {

puts("Could not find kernelentry point!\n");

return 1;

}

g_boot_running = 1;

return 0;

}

static intbootm_start_bh(void)

{

void        *os_hdr= gos_hdr;

int          ret;

printf("bootm_start_bh\n");

if (images.os.type ==IH_TYPE_KERNEL_NOLOAD) {

images.os.load =images.os.image_start;

images.ep += images.os.load;

}

if (((images.os.type == IH_TYPE_KERNEL)||

(images.os.type == IH_TYPE_KERNEL_NOLOAD) ||

(images.os.type == IH_TYPE_MULTI)) &&

(images.os.os == IH_OS_LINUX)) {

/* find ramdisk */

ret = boot_get_ramdisk(ARGC, (char* const*)g_argv, &images, IH_INITRD_ARCH,

&images.rd_start,&images.rd_end);

if (ret) {

puts("Ramdisk image iscorrupt or invalid\n");

return 1;

}

#ifdefined(CONFIG_OF_LIBFDT)

/* find flattened device tree */

ret = boot_get_fdt(FLAGS, ARGC,(char * const*)g_argv, &images,

&images.ft_addr, &images.ft_len);

if (ret) {

puts("Could not find avalid device tree\n");

return 1;

}

set_working_fdt_addr(images.ft_addr);

#endif

}

images.os.start = (ulong)os_hdr;

images.state = BOOTM_STATE_START;

return 0;

}

#endif /* CONFIG_IAUTO_PRELOAD_KNL*/

原始的bootm_start()函数改为分别执行bootm_start_th()和bootm_start_bh()来完成。

static intbootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

{

#ifdefined(CONFIG_IAUTO_PRELOAD_KNL)

if (!g_boot_running)

bootm_start_th();

return bootm_start_bh();

#else

......

#endif

}

在eMMC驱动中drivers/mmc/sh_mmcif.c的wait_read_dma_transfer()函数中增加标志位,如果kernel已经加载,则在等待过程中执行bootm_start_th()

#ifdefined(CONFIG_IAUTO_PRELOAD_KNL)

{ extern bool is_kernel_loaded(void);

if ( is_kernel_loaded() ){

extern intbootm_start_th(void);

bootm_start_th();

}}

#endif /*CONFIG_IAUTO_PRELOAD_KNL */

这样改动可以节省约几十ms,但是否值得。因为相关的实现不是很好,比如参数,比如对Kernel是否加载的判断等等。这有待今后有时间进一步优化。

接下来是关闭UART输出,考虑到在U-Boot中还需要支持串口交互及升级等,所以不可以像Miniboot一样,直接把UART驱动拿掉。用了个简单的方法,增加一个标志来决定是否真正的输出,如果标志为false,则输出函数直接返回。

修改drivers/serial/serial_sh.c中的sh_serial_putc()函数

static voidsh_serial_putc(const char c)

{

#ifdefined(CONFIG_IAUTO_OMIT_SERIAL_OUTPUT)

extern int g_enable_serial_out;

if (!g_enable_serial_out) return;

#endif /*CONFIG_IAUTO_OMIT_SERIAL_OUTPUT */

if (c == ‘\n‘)

serial_raw_putc(‘\r‘);

serial_raw_putc(c);

}

禁止输出标志,默认为禁止,在需要时才打开,目前来说,在检测到按键之后需要打开,在启动Kernel之前打开,输出一下提示,说明系统没有死机。

common/main.c文件main_loop()函数。在readline()之前把串口输出使能

g_sys_high_speed = 0; /* Set CPUclock to low, otherwise the USB enum will very slow. */

reset_timer();

#endif /*CONFIG_SYS_CLK_FREQ_HIGH */

#ifdefined(CONFIG_IAUTO_OMIT_SERIAL_OUTPUT)

g_enable_serial_out = 1;

#endif/* CONFIG_IAUTO_OMIT_SERIAL_OUTPUT */

len = readline(CONFIG_SYS_PROMPT);

flag = 0;       /* assume no special flags for now */

common/cmd_bootm.c在bootm_start()退出之前,打开允许

images.os.start = (ulong)os_hdr;

images.state = BOOTM_STATE_START;

#ifdefined(CONFIG_IAUTO_OMIT_SERIAL_OUTPUT)

{

extern int g_enable_serial_out;

g_enable_serial_out = 1;

}

#endif/* CONFIG_IAUTO_OMIT_SERIAL_OUTPUT */

return 0;

}

通过示波器测量,发现eMMC初始化的时间还是比较长的,大约在200ms左右。在看eMMC的驱动时,发现此驱动同时还支持SD设备,考虑到系统中没有SD设备,所以可以把SD相关内容删除或不执行。另外,把一些等待的步骤去除或者减少等待时间。相关代码在drivers/mmc/mmc.c中。

intmmc_init(struct mmc *mmc)

{

int err;

if (mmc_getcd(mmc) == 0) {

mmc->has_init = 0;

printf("MMC: no cardpresent\n");

return NO_CARD_ERR;

}

if (mmc->has_init)

return 0;

err = mmc->init(mmc);

if (err)

return err;

mmc_set_bus_width(mmc, 1);

mmc_set_clock(mmc, 1);

/* Reset the Card */

err = mmc_go_idle(mmc);

if (err)

return err;

/* The internal partition reset to userpartition(0) at every CMD0*/

mmc->part_num = 0;

#ifndefCONFIG_SH_MMCIF_NO_SD

/* Test for SD version 2 */

err = mmc_send_if_cond(mmc);

/* Now try to get the SD card‘s operatingcondition */

err = sd_send_op_cond(mmc);

/* If the command timed out, we check foran MMC card */

if (err == TIMEOUT)

#endif/* CONFIG_SH_MMCIF_NO_SD */

{

err = mmc_send_op_cond(mmc);

if (err) {

printf("Card did notrespond to voltage select!\n");

return UNUSABLE_ERR;

}

}

err = mmc_startup(mmc);

if (err)

mmc->has_init = 0;

else

mmc->has_init = 1;

return err;

}

static intmmc_send_op_cond(struct mmc *mmc)

{

int timeout = 10000;

struct mmc_cmd cmd;

int err;

/* Some cards seem to need this */

#ifndefCONFIG_SH_MMCIF_NO_SD

mmc_go_idle(mmc);

#endif/* !CONFIG_SH_MMCIF_NO_SD */

/* Asking to the card its capabilities */

cmd.cmdidx= MMC_CMD_SEND_OP_COND;

cmd.resp_type= MMC_RSP_R3;

cmd.cmdarg= 0;

err= mmc_send_cmd(mmc, &cmd, NULL);

if(err)

returnerr;

//udelay(1000);

do {

cmd.cmdidx = MMC_CMD_SEND_OP_COND;

cmd.resp_type = MMC_RSP_R3;

cmd.cmdarg = (mmc_host_is_spi(mmc)? 0 :

(mmc->voltages&

(cmd.response[0]& OCR_VOLTAGE_MASK)) |

(cmd.response[0]& OCR_ACCESS_MODE));

if (mmc->host_caps &MMC_MODE_HC)

cmd.cmdarg |= OCR_HCS;

err = mmc_send_cmd(mmc, &cmd,NULL);

if (err)

return err;

if ((cmd.response[0] &OCR_BUSY)) break;

udelay(300);

} while (!(cmd.response[0] &OCR_BUSY) && timeout--);

printf("do_op_cond done , timeout%d, resp %x\n",timeout,cmd.response[0]);

if (timeout <= 0)

return UNUSABLE_ERR;

if (mmc_host_is_spi(mmc)) { /* read OCRfor spi */

cmd.cmdidx = MMC_CMD_SPI_READ_OCR;

cmd.resp_type = MMC_RSP_R3;

cmd.cmdarg = 0;

err = mmc_send_cmd(mmc, &cmd,NULL);

if (err)

return err;

}

mmc->version = MMC_VERSION_UNKNOWN;

mmc->ocr = cmd.response[0];

mmc->high_capacity = ((mmc->ocr& OCR_HCS) == OCR_HCS);

mmc->rca = 0;

return 0;

}

以上工作完成后。U-Boot的时间节省近300ms,整个Boot的时间基本在700ms左右。

8、Miniboot中打开I-Cache

在看Miniboot的启动文件start.S时,发现没有开启I-Cache,就从U-Boot中把相关代码移到Miniboot中。Miniboot/common/start.S

#defineCONFIG_SYS_INIT_SP_ADDR           0xE633F000   // stack address for miniboot

.global _start

_start:

/*set the cpu to SVC32 mode, disablefiq&irq*/

mrs  r0,cpsr

bic   r0,r0, #0x1f

orr   r0,r0, #0xd3

msr  cpsr,r0

/*

*Invalidate L1 I/D

*/

mov r0,#0                    @ set up for MCR

mcr  p15,0, r0, c8, c7, 0       @ invalidate TLBs

mcr  p15,0, r0, c7, c5, 0       @ invalidate icache

mcr  p15,0, r0, c7, c5, 6       @ invalidate BParray

mcr    p15, 0, r0, c7, c10, 4     @ DSB

mcr    p15, 0, r0, c7, c5, 4       @ ISB

/*

*disable MMU stuff and caches

*/

mrc  p15,0, r0, c1, c0, 0

bic   r0,r0, #0x00002000      @ clear bits 13(--V-)

bic   r0,r0, #0x00000007      @ clear bits 2:0(-CAM)

bic   r0,r0, #0x00000002      @ clear bit 1 (--A-)Align

orr   r0,r0, #0x00000800      @ set bit 11 (Z---)BTB

orr   r0,r0, #0x00001000      @ set bit 12 (I)I-cache

mcr  p15,0, r0, c1, c0, 0

ldr   sp,=(CONFIG_SYS_INIT_SP_ADDR)

/* Clear the .bss section (zero init) */

LDR    r1,=__bss_start

LDR    r2,=__bss_end

MOV    r3,#0

1:

CMP    r1,r2

STMLTIA r1!,{r3}

BLT    1b

/* Branch to C Library entry point */

bl    boot_main

/* We should never reach here */

b  .

这一下子又把Miniboot的时间减少了近60~70ms。总的时间减少到630~640ms左右。如果把Kernel+Ramdisk减到5MB左右的话,基本上可以实现400ms左右启动时间。

9、关于直接写寄存器进行GPIO操作

在调试过程中,由于通过gpio_request()等方法操作GPIO需要等待环境都Ready之后才可以进行,所以在系统初始化时无法调用,调用的话可能会引起死机。所以决定通过写相关的寄存器直接来操作GPIO。

Renesas的CPU操作GPIO和其它的平台相比有一点古怪,一般的平台,设置一个PIN为输出GPIO基本步骤如下:

设置PIN脚模式为GPIOà把PIN脚的模式设置为输出à设置输出为高/低

Renesas的也基本遵循这个步骤,但在最前还需要设置一个类似使能寄存器的寄存器LSI Multiplexed Pin Setting MaskRegister(PMMR)。而且此寄存器的使用非常古怪,和一般的理解不同,并不是简单的0/1有效,而是和设置值反时有效。即,如果你想设置为0x1F,则需要先设置PMMR为~0x1F,即0xE0。相关说明见下:

设置PIN脚功能寄存器GPIO/Peripheral Function Select Register(GPSRn)和一般的没什么区别,相应位置0为GPIO、为1则作为功能管脚。

GPIO设置涉及以下寄存器,GeneralIO/Interrupts Switching Register (IOINTSELn)选择GPIO还是中断输出模式,General Input/Output Switching Register(INOUTSELn)选择是输入还是输出,General Output Register(OUTDTn)输出高/低,Positive/NegativeLogic Select Register(POSNEGn)选择输出极性。

示例代码如下:

#ifdefCONFIG_SYS_BOOT_TP_GPIO

/* Notice:PFC_PMMR must use the negative code of setting value. full 0x00/0xFF does‘tworking.

必须设置为反码,否则无效 !!!!! */

#define    SetGuardREG(addr, value)           \

{ \

writel(~value,PFC_PMMR); \

writel(value, addr); \

}

static void_pfc_init(void)

{

#define PFC_BASE                0xE6060000

#definePFC_PMMR                PFC_BASE + 0

#definePFC_GPSR4               PFC_BASE + 0x14

static int  b_init = 0;

if ( b_init ) return;

int val = readl(PFC_GPSR4) &0xFFFFDFFF; // 0x5BFFDFFF

SetGuardREG(PFC_GPSR4, val );

b_init = 1;

}

static void_set_gpio(int onoff)

{

#defineGPIO_BASE               0xE6050000

#defineGPIO_4_BASE             (GPIO_BASE +0x4000)

#defineGPIO_IOINTSEL(pfx)     (GPIO_##pfx##_BASE + 0x0)

#defineGPIO_INOUTSEL(pfx)      (GPIO_##pfx##_BASE+ 0x4)

#defineGPIO_OUTDT(pfx)        (GPIO_##pfx##_BASE + 0x8)

#defineGPIO_POSNEG(pfx)       (GPIO_##pfx##_BASE + 0x20)

int val;

/* Notice: before set GPIO, need set theGPSR# register, select GPIO/Peripheral mode */

_pfc_init();

writel(0x00000000, GPIO_POSNEG(4));

writel(0x00000000, GPIO_IOINTSEL(4));

val = readl(GPIO_OUTDT(4));

if (onoff) val |= 0x00002000;

else val &= 0xFFFFDFFF;

writel(val, GPIO_OUTDT(4));

writel(readl(GPIO_INOUTSEL(4)) | 0x2000,GPIO_INOUTSEL(4));

}

#endif /*CONFIG_SYS_BOOT_TP_GPIO */

后记:理解了GPIO的寄存器设置方法后,总算明白了在Miniboot和Uboot之间低电平是怎么来的,之前一直以为是Uboot启动之后设置的,所以不可理解为什么Uboot从启动到设置高电平需要近80ms的时间。

实际上第一个高电平是SoC上电。系统默认情况下,此PIN输出为高电平,之后等待ROM中代码从SPI中读取16K代码到IRAM,然后开始执行,在Miniboot中的gpio_setting()中把相应的GPIO拉低,所以事实上ROM执行了近60ms(效率明显不高啊),之后的80ms是Miniboot的执行时间。之后再拉高是在U-Boot的board_init中完成的。在U-Boot中没有复位之类的步骤,总算解释了之前的疑惑。

10、更多

考虑到eMMC的初始化时间比较长,主要是在等待eMMC控制器Ready,所以可以把eMMC的初始化分成两部分,把等待之前的部分放在Miniboot中执行,然后再加载U-Boot到内存,跳到U-Boot执行,在U-Boot中只需要再去查询一下eMMC控制器的状态,就绪后就可以进行下面的操作了。为了适用不同的Miniboot,U-Boot中需要判断一下Miniboot中是否已经执行了相关操作,然后决定是否省掉重复的步骤。

这部分移植代码较多,在此不列了,完成后,可以减少Miniboot加载U-Boot的时间,在没有打开I-Cache之前可能是60-70ms,打开后,可能是20ms左右。

Miniboot中运行的一些结果,也可以通过写在RAM的固定位置的方式传到U-Boot中。参考代码如下:miniboot/paris3e2/board.c的boot_main()函数

#defineCONFIG_UBOOT_OFFSET        0x20000

#defineCONFIG_UBOOT_LOADADDR    0xe6304000

#defineLOAD_UBOOT_SIZE                   (230*1024)

#defineMINIBOOT_VER                   "0.3c"

#ifdefined(MINIBOOT_NO_UART) || defined(PRE_INIT_EMMC)

char *pver= (char *)(CONFIG_UBOOT_LOADADDR-32);

#endif/* MINIBOOT_NO_UART | PRE_INIT_EMMC */

board_init();

print("\nMiniboot Version--%s(%s)\n",MINIBOOT_VER,GIT_SHA1_ID);

#ifdef MINIBOOT_NO_UART

*pver ++ =‘M‘;

*pver ++ =‘B‘;

memcpy(pver,MINIBOOT_VER,sizeof(MINIBOOT_VER)-1);

pver += sizeof(MINIBOOT_VER)-1;

*pver ++ =‘(‘;

memcpy(pver,GIT_SHA1_ID,sizeof(GIT_SHA1_ID)-1);

pver += sizeof(GIT_SHA1_ID)-1;

*pver ++ =‘)‘;

*pver ++ =0x0;

#endif /* MINIBOOT_NO_UART */

#ifdef PRE_INIT_EMMC

pver = (char*)(CONFIG_UBOOT_LOADADDR-32);

{ int val;

val = readl(CONFIG_SH_MMC0CK);

debug("mmcclk current value0x%X\n", val);

val &= MMC0CK_MASK;

val |= MMCCLK_RATIO;

writel(val, CONFIG_SH_MMC0CK);

}

mmcif_mmc_init();

if (!mmc_init(&g_mmc, (int*)(CONFIG_UBOOT_LOADADDR-8))) *pver = ‘N‘;

#endif /* PRE_INIT_EMMC */

在上面的代码中,把Miniboot的版本号以及执行mmc_init()之后的结果放在固定内存位置0xe6304000-32 ~ 0xe6304000。

在U-boot中,arch/arm/lib/board.c中的display_banner()函数

#defineCONFIG_UBOOT_LOADADDR  0xe6304000      /* U-Boot start address */

#define MINIBOOT_VER_ADDR              (char*)(CONFIG_UBOOT_LOADADDR-32)      /*Miniboot version string address */

static intdisplay_banner(void)

{

char *pver= MINIBOOT_VER_ADDR;

if (( ‘M‘ == *pver || ‘N‘ == *pver )&& ‘B‘ == *(pver+1) ) {

#ifdefCONFIG_EMMC_MINIBOOT_INIT

printf("\n%cMB %s(%x),UB(%s)",(‘N‘==*pver?‘*‘:‘ ‘),pver+2, g_emmc_resp,GIT_SHA1_ID );

#else

printf("\nMB %s,UB(%s)",pver+2, GIT_SHA1_ID );

#endif/* CONFIG_EMMC_MINIBOOT_INIT */

} else

printf("\n\n%s\n\n",version_string);

debug("U-Boot code: %08lX ->%08lX  BSS: -> %08lX\n",

_TEXT_BASE,

_bss_start_ofs + _TEXT_BASE, _bss_end_ofs + _TEXT_BASE);

#ifdefCONFIG_MODEM_SUPPORT

debug("Modem Supportenabled\n");

#endif

#ifdefCONFIG_USE_IRQ

debug("IRQ Stack: %08lx\n", IRQ_STACK_START);

debug("FIQ Stack: %08lx\n",FIQ_STACK_START);

#endif

return (0);

}

board/renesas/Paris3E2.c中board_early_init_f()函数

intboard_early_init_f(void)

{

#ifdefCONFIG_EMMC_MINIBOOT_INIT

g_emmc_pre_init = ( ‘N‘ ==*MINIBOOT_VER_ADDR );

g_emmc_resp     = *MINIBOOT_PARA_ADDR;

if (!g_emmc_pre_init)

#endif/* CONFIG_EMMC_MINIBOOT_INIT */

set_all_module_cpg();

}

一些个人体会

由于现代CPU的主频比较高(此CPU为1GHz),所以如果是非I/O类的代码执行几百行或更多行并不会消耗太多的时间,基本都是在ms级的。所以局部的优化代码意义并不大。但CPU的I/D-Cache对速度影响非常明显,I-Cache打开非常简单,基本上是寄存器设置一下即可,D-Cache由于涉及到MMU,所以在此没有涉及。

主要的时间花在地外设的I/O过程中,所以第一要注意的是Clock是否正常,Data线是否已经全部用上,另外交互流程是否存在不需要的步骤。几次时间的大量节省都是此改善引起的。

DMA模式并不一定节省时间,只是可以释放CPU来处理其它一些事情,所以实际使用时需要考虑CPU是否有其它并行任务可以运行,另外实际测试一下开启和关闭DMA情况的实际性能再做决定。

串口由于速度慢,一般是115200bps,所以操作上也比较耗时间,尽量减少不必要的LOG输出。在Release版本时,可以考虑关闭串口。一般会有明显的时间节省。

待解问题

1、为什么Flash会进入OTP状态,几次出现都是在改为97Mhz时钟之后,但具体原因不明。

2、SYS_CLOCK的作用机制,为什么改为1G后会变快,但调用udelay()函数的地方事实上是变慢了。

3、没有测试在Miniboot中开启QSPI DMA操作方式是否会有提升。

时间: 2024-11-09 11:33:22

Renesas E2 平台启动时间调试的相关文章

展讯7731平台驱动调试总结-驱动配置部分

转载至:http://blog.csdn.net/bmw7bmw7/article/details/46126223 展讯7731平台驱动调试总结-驱动配置部分 1. 关键配置文件路径 1). 项目板级配置:idh.code/device/sprd/scx35_sp7731geaplus_pad_qhd/文件夹内各文件 ⑴.BoardConfig.mk---板级宏配置文件.包括设置该板(项目)所使用的uboot/kerenl全局配置宏文件,摄像头接口类型.分辨率等参数,所使用的重力加速度.光线传

MTK65XX平台充电调试总结

MTK平台充电调试总结 摘要:调试电池的充放电管理,首先需要深入了解锂电池的电池原理和特点,充放电特性以及基本的电池安全问题.然后需要对MTK的电池管理驱动程序有深入的了解,理解电池充放电算法的基本原理.在此基础上,对充放电导致的问题进行调试优化. 一. 锂电池工作原理和特性 1.工作原理: 锂离子电池以碳素材料为负极,以含锂的化合物作正极.它主要依靠锂离子在正极和负极之间移动来工作.在充放电过程中,Li+ 在两个电极之间往返嵌入和脱嵌:充电时,Li+从正极脱嵌,经过电解质嵌入负极,负极处于富锂

微信公众平台开发调试方法

在这篇微信公众平台开发教程中,我们将介绍如何进行微信公众平台上的开发调试方法. 一.方倍微信调试器 微信调试器是方倍工作室开发的用于微信公众平台接口开发调试的工具,具有Token校验.模拟关注及取消关注.发送文本/图片/语音/视频/位置/链接.模拟事件发送等功能.其原型是微擎的Emulator. 微信调试器目前不支持IE浏览器下的使用,请下载Chrome浏览器或下载Firefox浏览器. 微信调试器的地址是 http://debug.fangbei.org/ .其界面如下 使用方法 1. Tok

.NET平台原理调试分析【一】WinDBG+OllyDBG by:凉游浅笔深画眉

我在.NET平台诞生了12年后才接触到它,而国内.NET平台的逆向技术兴起于10年前. 我是不幸的,不幸的是起步比前辈们晚了10年左右,十分后悔当初把最好的学习时间奉献给了游戏.最后发现,玩再多游戏也并没什么卵用. 我也是幸运的,幸运的是前人已留下了太多对.NET平台的探索脚印.我只需要跟随着他们的脚步快速前进,而不会绕很多弯路和四处碰壁. 最后感谢那些写过文章供后人学习的前辈们,虽然很多文章都是七八年前的,但受用至今. 此文既不是破解,也不讲脱壳,只是逆向角度分析.NET程序执行原理.仅希望抛

Qualcomm平台camera调试移植入门

1  camera基本代码架构 高通平台对于camera的代码组织,大体上还是遵循Android的框架:即上层应用和HAL层交互,高通平台在HAL层里面实现自己的一套管理策略: 在kernel中实现sensor的底层驱动.但是,对于最核心的sensor端的底层设置.ISP效果相关等代码则是单独进行了抽离,放在了一个 daemon进程中进行管理: 图1 Qualcomm平台camera代码架构简图 由于高通把大部分具体的设置及参数放到了daemon进程中,所以在kernel部分只是进行了V4L2的

高通平台Camera调试(一)【转】

本文转载自:http://www.voidcn.com/blog/Winva/article/p-6044730.html 4.3. Camera 参考文档: 1) 80-NA157-22_PRESENTATION- MSM8974-APQ8074-MSM8X26-APQ8084 LINUX CAMERA OVERVIEW.pdf 2) 80-NE717-1_MSM8974-APQ8074-MSM8X26 LINUX CAMERA SOFTWARE DESIGN DOCUMENT.pdf 3)

展讯7731平台驱动调试总结(2)---- 驱动配置部分

1. 关键配置文件路径 1). 项目板级配置:idh.code/device/sprd/scx35_sp7731geaplus_pad_qhd/文件夹内各文件 ⑴.BoardConfig.mk---板级宏配置文件.包括设置该板(项目)所使用的uboot/kerenl全局配置宏文件,摄像头接口类型.分辨率等参数,所使用的重力加速度.光线传感器,内部存储空间大小分配等. ⑵. init.board.rc---板级自定义启动服务文件(TP,传感器等设备的启动指令放置在此文件中) ⑶.system.pr

MTK平台驱动调试指南

Ø      GPIO设置篇 一.GPIO有关的函数 1.GPIO_ModeSetup: 函数原型:void GPIO_ModeSetup(kal_uint16 pin, kal_uint16 conf_dada) 功能:设置GPIO的工作模式是作为GPIO,还是作为专有功能接口. 参数: pin:GPIO 的pin脚号,对应于原理图上MTK62XX 主CPU芯片的上的GPIO标号 conf_dada:值有0~3.其中0是表示作为GPIO模式,其他根据专有功能的不同进行设置. 2.GPO_Ini

Android : 高通平台Camera调试

相关平台信息:[SDM845/670] 1.AF调试部分 (1)通过属性控制: --AF Log控制--setprop persist.vendor.camera.logInfoMask 0x8000000 //AF info level log setprop persist.vendor.camera.logVerboseMask 0x8000000 //AF verbose level log --禁用深度焦点指示--setprop persist.vendor.camera.af.dep