码迷,mamicode.com
首页 > 其他好文 > 详细

Renesas E2 平台启动时间调试

时间:2016-05-27 11:30:42      阅读:234      评论:0      收藏:0      [点我收藏+]

标签:

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

 

 

 

 

 

Renesas E2 平台启动时间调试

标签:

原文地址:http://blog.csdn.net/u013129593/article/details/51507992

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!