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操作方式是否会有提升。