DM365是的启动方式有两种,通过BOOTSEL[2:0]引脚决定。当其为001时,直接从AEMIF上启 动,比如NOR和OneNAND。除此之外皆是从RBL启动,顺序为RBL-UBL-UBOOT-KERNEL,比如NAND,串口,SD卡等。RBL会 搜寻block1到block24去找UBL,关于RBL启动的详细细节可以参考用户指南关于ARM子系统的那篇文档,很详尽,下面只分析UBL的源码。
UBL源码在PSP包里的board_utilities\flash_utils目录下,主要是COMMON目录和各子平台的目录如DM36x等,内中除了UBL的源码外还有CCS下JTAG的擦除烧写源码,串口烧写源码等。下面只分析UBL的启动代码。
入门代码是汇编文件start.S,主要是切换操作模式,建立堆栈等,然后跳转到main函数,进入到Common\ubl\src目录下的C文件ubl.c中。main函数如下:
// Main entry point
void main(void) { // Call to real boot function code
LOCAL_boot(); // Jump to entry point
DEBUG_printString("\r\nJumping to entry point at "); DEBUG_printHexInt(gEntryPoint); DEBUG_printString(".\r\n"); APPEntry = (void(*)(void)) gEntryPoint; (*APPEntry)(); } |
main函数主要调用了LOCAL_boot函数来进行实质的引导功能,下面是此函数的内容:
static Uint32 LOCAL_boot(void) { DEVICE_BootMode bootMode; // Read boot mode
bootMode = DEVICE_bootMode(); if (bootMode== DEVICE_BOOTMODE_UART) { // Wait until the RBL is done using the UART.
while((UART0->LSR& 0x40)== 0 ); } // Platform Initialization
if ( DEVICE_init()!= E_PASS) { DEBUG_printString(devString); DEBUG_printString(" initialization failed!\r\n"); asm(" MOV PC, #0"); } else { DEBUG_printString(devString); DEBUG_printString(" initialization passed!\r\n"); } // Set RAM pointer to beginning of RAM space
UTIL_setCurrMemPtr(0); // Send some information to host
DEBUG_printString("TI UBL Version: "); DEBUG_printString(UBL_VERSION_STRING); DEBUG_printString("\r\nBooting Catalog Boot Loader\r\nBootMode = "); // Select Boot Mode
#if defined(UBL_NAND) { //Report Bootmode to host
DEBUG_printString("NAND\r\n"); // Copy binary image application from NAND to RAM
if (NANDBOOT_copy()!= E_PASS) { DEBUG_printString("NAND Boot failed.\r\n"); LOCAL_bootAbort(); } } #elif defined(UBL_NOR) { //Report Bootmode to host
DEBUG_printString("NOR \r\n"); // Copy binary application image from NOR to RAM
if (NORBOOT_copy()!= E_PASS) { DEBUG_printString("NOR Boot failed.\r\n"); LOCAL_bootAbort(); } } #elif defined(UBL_SD_MMC) { //Report Bootmode to host
DEBUG_printString("SD/MMC \r\n"); // Copy binary of application image from SD/MMC card to RAM
if (SDMMCBOOT_copy()!= E_PASS) { DEBUG_printString("SD/MMC Boot failed.\r\n"); LOCAL_bootAbort(); } } #else { //Report Bootmode to host
DEBUG_printString("UART\r\n"); UARTBOOT_copy(); } #endif DEBUG_printString(" DONE"); UTIL_waitLoop(10000); DEVICE_TIMER0Stop(); return E_PASS; } |
先通过调用DEVICE_bootMode函数来判断启动方式(通过读取SYS寄存器实现),而后调用了DEVICE_init函数来进行平台的最底层初始化,包括电源域,时钟,DDR,EMIF,UART,I2C,TIMER等,另有专篇分析。
而后通过UTIL_setCurrMemPtr函数对全局变量currMemPtr赋值,以后用到。接着通过判断不同的引导方式,采取不同的处理办法,以 NAND启动为例,将调用NANDBOOT_copy函数,此函数另有专篇分析。此函数将NAND中的某些内容(就是UBOOT)搬移到RAM中,而后 UBL结束,控制权正式交给UBOOT。
书接上回,看看UBL对平台的初始化,主要是调用了DEVICE_init函数,函数内容如下:
Uint32 DEVICE_init() { Uint32 status = E_PASS; // Mask all interrupts AINTC->INTCTL= 0x4; AINTC->EABASE= 0x0; AINTC->EINT0= 0x0; AINTC->EINT1= 0x0; // Clear all interrupts AINTC->FIQ0= 0xFFFFFFFF; AINTC->FIQ1= 0xFFFFFFFF; AINTC->IRQ0= 0xFFFFFFFF; AINTC->IRQ1= 0xFFFFFFFF; #ifndef SKIP_LOW_LEVEL_INIT POR_RESET(); // System PSC setup - enable all DEVICE_PSCInit(); DEVICE_pinmuxControl(0,0xFFFFFFFF,0x00FD0000);// All Video Inputs
DEVICE_pinmuxControl(1,0xFFFFFFFF,0x00145555);// All Video Outputs
DEVICE_pinmuxControl(2,0xFFFFFFFF,0x000000DA);// EMIFA
DEVICE_pinmuxControl(3,0xFFFFFFFF,0x00180000);// SPI0, SPI1, UART1, I2C, SD0, SD1, McBSP0, CLKOUTs
DEVICE_pinmuxControl(4,0xFFFFFFFF,0x55555555);// MMC/SD0 instead of MS, SPI0
GPIO->DIR02&= 0xfeffffff; GPIO->CLRDATA02= 0x01000000; // System PLL setup if (status== E_PASS) status|= DEVICE_PLL1Init(PLL1_Mult); // DDR PLL setup if (status== E_PASS) status|= DEVICE_PLL2Init(); // DDR2 module setup if (status== E_PASS) status|= DEVICE_DDR2Init(); #endif // AEMIF Setup if (status== E_PASS) status|= DEVICE_EMIFInit(); // UART0 Setup if (status== E_PASS) status|= DEVICE_UART0Init(); // TIMER0 Setup if (status== E_PASS) status|= DEVICE_TIMER0Init(); // I2C0 Setup if (status== E_PASS) status|= DEVICE_I2C0Init(); return status; } |
首先屏蔽和清除中断,然后调用DEVICE_PSCInit函数实现对各模块的电源时钟使能,实质是调用PSC电源时钟管理模块的寄存器实现,函数内容如下:
void DEVICE_PSCInit() { unsigned char i=0; unsigned char lpsc_start; unsigned char lpsc_end,lpscgroup,lpscmin,lpscmax; unsigned int PdNum= 0; lpscmin =0; lpscmax =2; for(lpscgroup=lpscmin; lpscgroup<=lpscmax; lpscgroup++){ if(lpscgroup==0) { lpsc_start = 0;// Enabling LPSC 3 to 28 SCR first
lpsc_end = 28; } else if(lpscgroup== 1){/* Skip locked LPSCs [29-37] */ lpsc_start = 38; lpsc_end = 47; } else{ lpsc_start = 50; lpsc_end = 51; } //NEXT=0x3, Enable LPSC's for(i=lpsc_start; i<=lpsc_end; i++){ PSC->MDCTL[i]|= 0x3; } //Program goctl to start transition sequence for LPSCs PSC->PTCMD=(1<<PdNum); //Wait for GOSTAT = NO TRANSITION from PSC for Pdomain 0 while(!(((PSC->PTSTAT>> PdNum)& 0x00000001)== 0)); //Wait for MODSTAT = ENABLE from LPSC's for(i=lpsc_start; i<=lpsc_end; i++){ while(!((PSC->MDSTAT[i]& 0x0000001F)== 0x3)); } } } |
然后调用DEVICE_pinmuxControl函数决定复用引脚的功能选择,详见数据手册查看引脚功能。
接着调用DEVICE_PLL1Init函数实现了PLL1的配置,预分频,倍频,后分频,分频到各个模块,其设置顺序可以参看用户指南ARM子系统文档,有详细的介绍,PLL2类似不再赘述,函数内容如下:
Uint32 DEVICE_PLL1Init(Uint32 PLLMult)
{
unsigned int CLKSRC=0x0;
unsigned int j;
/*Power up the PLL*/
PLL1->PLLCTL&= 0xFFFFFFFD;
PLL1->PLLCTL&= 0xFFFFFEFF;
PLL1->PLLCTL|= CLKSRC<<8;
/*Set PLLENSRC '0', PLL Enable(PLLEN) selection is controlled through MMR*/
PLL1->PLLCTL&= 0xFFFFFFDF;
/*Set PLLEN=0 => PLL BYPASS MODE*/
PLL1->PLLCTL&= 0xFFFFFFFE;
UTIL_waitLoop(150);
// PLLRST=1(reset assert)
PLL1->PLLCTL|= 0x00000008;
UTIL_waitLoop(300);
/*Bring PLL out of Reset*/
PLL1->PLLCTL&= 0xFFFFFFF7;
//Program the Multiper and Pre-Divider for PLL1
PLL1->PLLM= 0x51;// VCO will 24*2M/N+1 = 486Mhz
PLL1->PREDIV= 0x8000|0x7;
PLL1->SECCTL= 0x00470000;// Assert TENABLE = 1, TENABLEDIV = 1, TINITZ = 1
PLL1->SECCTL= 0x00460000;// Assert TENABLE = 1, TENABLEDIV = 1, TINITZ = 0
PLL1->SECCTL= 0x00400000;// Assert TENABLE = 0, TENABLEDIV = 0, TINITZ = 0
PLL1->SECCTL= 0x00410000;// Assert TENABLE = 0, TENABLEDIV = 0, TINITZ = 1
//Program the PostDiv for PLL1
PLL1->POSTDIV= 0x8000;
// Post divider setting for PLL1
PLL1->PLLDIV2= 0x8001;
PLL1->PLLDIV3= 0x8001;// POST DIV 486/2 -> MJCP/HDVICP
PLL1->PLLDIV4= 0x8003;// POST DIV 486/4 -> EDMA/EDMA CFG
PLL1->PLLDIV5= 0x8001;// POST DIV 486/2 -> VPSS
PLL1->PLLDIV6= 0x8011;// 27Mhz POST DIV 486/18 -> VENC
PLL1->PLLDIV7= 0x8000;// POST DIV 486/2 -> DDR
PLL1->PLLDIV8= 0x8003;// POST DIV 486/4 -> MMC0/SD0
PLL1->PLLDIV9= 0x8001;// POST DIV 486/2 -> CLKOUT
UTIL_waitLoop(300);
/*Set the GOSET bit */
PLL1->PLLCMD= 0x00000001;// Go
UTIL_waitLoop(300);
/*Wait for PLL to LOCK */
while(!(((SYSTEM->PLL0_CONFIG)& 0x07000000)== 0x07000000));
/*Enable the PLL Bit of PLLCTL*/
PLL1->PLLCTL|= 0x00000001;// PLLEN=0
return E_PASS;
}
继续在DEVICE_init函数中,下面是调用DEVICE_DDR2Init函数来配置DDR控制器,这是 UBL中重要的一部分,如果硬件电路需要更换内存芯片的话,需要在UBL中修改这个函数,即按照芯片手册来配置DDR控制寄存器中的相关参数,比如时 序,BANK数,页大小等。这个函数主要是操作SYS模块和DDR模块的相关寄存器来配置内存,函数中调用的DEVICE_LPSCTransition 函数用来实现模块的电源时钟状态的改变,函数内容如下:
Uint32 DEVICE_DDR2Init() { DEVICE_LPSCTransition(LPSC_DDR2,0,PSC_ENABLE); SYSTEM->VTPIOCR=(SYSTEM->VTPIOCR)& 0xFFFF9F3F; // Set bit CLRZ (bit 13) SYSTEM->VTPIOCR=(SYSTEM->VTPIOCR)| 0x00002000; // Check VTP READY Status while( !(SYSTEM->VTPIOCR& 0x8000)); // Set bit VTP_IOPWRDWN bit 14 for DDR input buffers) //SYSTEM->VTPIOCR = SYSTEM->VTPIOCR | 0x00004000;
// Set bit LOCK(bit7) and PWRSAVE (bit8) SYSTEM->VTPIOCR=SYSTEM->VTPIOCR| 0x00000080; // Powerdown VTP as it is locked (bit 6) // Set bit VTP_IOPWRDWN bit 14 for DDR input buffers) SYSTEM->VTPIOCR=SYSTEM->VTPIOCR| 0x00004040; // Wait for calibration to complete UTIL_waitLoop( 150 ); // Set the DDR2 to synreset, then enable it again DEVICE_LPSCTransition(LPSC_DDR2,0,PSC_SYNCRESET); DEVICE_LPSCTransition(LPSC_DDR2,0,PSC_ENABLE); DDR->DDRPHYCR= 0x000000C5; DDR->SDBCR= 0x08D34832; //Program SDRAM Bank Config Register DDR->SDBCR= 0x0853C832; DDR->SDTIMR=0x3C934B51; //Program SDRAM Timing Control Register1 DDR->SDTIMR2=0x4221C72; //Program SDRAM Timing Control Register2 DDR->PBBPR= 0x000000FE; DDR->SDBCR= 0x08534832; //Program SDRAM Bank Config Register DDR->SDRCR= 0x00000768; //Program SDRAM Refresh Control Register DEVICE_LPSCTransition(LPSC_DDR2,0,PSC_SYNCRESET); DEVICE_LPSCTransition(LPSC_DDR2,0,PSC_ENABLE); return E_PASS; }
void DEVICE_LPSCTransition(Uint8 module, Uint8 domain, Uint8 state) { // Wait for any outstanding transition to complete while ((PSC->PTSTAT)&(0x00000001<< domain)); // If we are already in that state, just return if (((PSC->MDSTAT[module])& 0x1F) == state)return; // Perform transition PSC->MDCTL[module]=((PSC->MDCTL[module])&(0xFFFFFFE0))|(state); PSC->PTCMD|=(0x00000001 << domain); // Wait for transition to complete while ((PSC->PTSTAT)&(0x00000001<< domain)); // Wait and verify the state while (((PSC->MDSTAT[module])& 0x1F) != state); } |
而后调用DEVICE_EMIFInit函数来配置EMIF模块,这个模块用来接外存,比如NAND,NOR等。DM365有两个片选空间,如果某一空间配置成NAND,则需要在寄存器中设置,其函数内容如下:
Uint32 DEVICE_EMIFInit() { AEMIF->AWCCR= 0xff; AEMIF->A1CR= 0x40400204; AEMIF->NANDFCR|= 1; AEMIF->A2CR= 0x00a00505; return E_PASS; } |
而后调用DEVICE_UART0Init函数来配置串口0,调用DEVICE_TIMER0Init函数来配置TIMER0,调用 DEVICE_I2C0Init函数来配置I2C控制器,都是操作某一模块的控制寄存器实现,具体如何设置可以参考相关模块的手册,这三个函数的内容如 下:
Uint32 DEVICE_UART0Init() { UART0->PWREMU_MGNT= 0;// Reset UART TX & RX components
UTIL_waitLoop( 100 ); UART0->MDR= 0x0; UART0->DLL= 0xd;// Set baud rate UART0->DLH= 0; UART0->FCR= 0x0007;// Clear UART TX & RX FIFOs UART0->FCR= 0x0000;// Non-FIFO mode UART0->IER= 0x0007;// Enable interrupts
UART0->LCR= 0x0003;// 8-bit words // 1 STOP bit generated, // No Parity, No Stick paritiy, // No Break control UART0->MCR= 0x0000;// RTS & CTS disabled, // Loopback mode disabled, // Autoflow disabled UART0->PWREMU_MGNT= 0xE001;// Enable TX & RX componenets return E_PASS; }
Uint32 DEVICE_I2C0Init() { I2C0->ICMDR= 0;// Reset I2C I2C0->ICPSC= 26;// Config prescaler for 27MHz I2C0->ICCLKL= 20;// Config clk LOW for 20kHz I2C0->ICCLKH= 20;// Config clk HIGH for 20kHz I2C0->ICMDR|= I2C_ICMDR_IRS;// Release I2C from reset return E_PASS; }
Uint32 DEVICE_TIMER0Init() { // Put timer into reset TIMER0->EMUMGT_CLKSPD= 0x00000003; TIMER0->TCR= 0x00000000; // Enable TINT0, TINT1 interrupt TIMER0->INTCTL_STAT= 0x00000001; // Set to 64-bit GP Timer mode, enable TIMER12 & TIMER34 TIMER0->TGCR= 0x00000003; // Reset timers to zero TIMER0->TIM12= 0x00000000; TIMER0->TIM34= 0x00000000; // Set timer period (5 second timeout = (24000000 * 5) cycles = 0x07270E00) TIMER0->PRD34= 0x00000000; TIMER0->PRD12= 0x07270E00; return E_PASS; } |
至此,DEVICE_init函数结束,程序返回至LOCAL_boot函数中,接着就调用NANDBOOT_copy函数了。
下面继续分析,由于后面的代码和函数量相对的多且复杂,所以不再贴上代码,只说流程,只要把程序源码打开对着看很容易就明白了。
在DEVICE_init()这个大的平台初始化函数结束以后,下面要做的工作就是将NAND的中UBOOT复制到DDR中并且跳转到那里,然后UBL的历史使命结束,UBOOT的执行开始,那么就是调用这个函数,NANDBOOT_copy()。
NANDBOOT_copy()函数主要分三大块,第一大块是调用NAND_open()函数对NAND控制器和NAND芯片进行初始化,它传递的参数是CE的地址(一般是CE0)和总线位宽(一般是8位)。好,我们跳到NAND_open()函数中。
NAND_open()函数在common\driver\src\nand.c中,与平台无关。首先定义一个NAND_InfoHandle结构 hNandInfo,这个结构包含了所有与NAND有关的参数,比如厂商ID,设备ID,基址,位宽,块数,页数每块,页大小及与ECC,BB有关的一些 函数。然后对这个hNandInfo进行赋值,这里最重要的是把 DEVICE_NAND_PAGE_layout,DEVICE_NAND_ECC_info,DEVICE_NAND_BB_info,DEVICE_NAND_CHIP_infoTable 这四大全局结构赋给了hNandInfo的相关字段。这四大全局结构在每个平台目录下都有定义(比如dm36x\common\src \device_nand.c),编译不同平台的UBL,会将不同平台的四大全局结构传递给nand.c中使用,这样就做到了NAND的驱动分离中平台无 关部分和平台有关部分,这种思想也贯穿在整个LINUX的架构中,先不提这个。然后,操作AEMIF寄存器使能NAND的哪一个CE中,这个由传递的基址 决定,一般是CE0,并使能这段片选之上有ECC功能。
接着调用NAND_reset()函数对NAND芯片进行复位,这个函数本身很简单,就是先发复位命令0xFF,再等待操作完成,懂得NAND操作过程的 会很容易这个操作。但是这里多说一点,一般我们遇到的芯片操作NAND,其CPU内部会有一个NAND控制器,对NAND的操作会通过这个寄存器来操作, 发命令发地址等都是操作这些寄存器来完成。但是DM365不是这样,它没有实质上的NAND控制器,它还是将NAND当做是一个类RAM一样的操作,通过 总线上的时序来完成。CLE和ALE连在地址总线A2和A1上,这样的话发命令实质上就是在总线地址0x00000010u上写值(注意DM365的地址 线问题,有个32位问题),发地址实质就是在总线地址0x00000008u上写值,读写数据实质就是在总线地址0x00000000u上读写值。这些地 址的操作隐含着CLE和ALE的高低电平,再加上配好的时序参数就把NAND的操作弄成了类RAM的操作。这种操作以前没有碰到过,TI的工程师很牛。
接着调用LOCAL_flashGetDetails()函数,这个函数很重要,它会得到NAND的很多详细信息。首先它会判断NAND是否是ONFI, 这个没有细究过,跳过不管。然后它会向NAND发读ID命令,从而得到NAND的厂商ID和设备ID值。有了这两个值就好办了,通过一个循环把它们通四全 大局结构中的DEVICE_NAND_CHIP_infoTable中定义的各种NAND的资料进行对比,通过ID来定位是哪一种NAND,然后就得出了 页数,块数,页大小等重要信息。另外,还要区分NAND是大型还是小型(一般看页大小,大于2K为大),因为它们的地址周期不同。这个函数后面还有一些东 西,没有深究。调用完成后再复位一下NAND,整个NAND_open()函数就结束了。
NAND_open()函数完成后,会调用NAND_readPage()函数从第25块开始一直搜索到第50块,只读第0页。因为UBOOT放在第25 块开始的地方,这是手册规定的,而且第0页放一些启动信息等等。通过MAGIC_NUMBER来判断是否有效,这个知识点详见手册。一旦 MAGIC_NUMBER正确,就会将后面的几个字段,比如UBOOT的进入点,页数,UBOOT在哪一个块,哪一个页,UBOOT的装载点等信息就会得 到。而后循环调用NAND_readPage()函数将整个UBOOT从NAND拷贝到DDR里,最后将刚才得到的UBOOT的进入点地址复制到 gEntryPoint变量中。然后主函数跳转到这里,从此UBL结束,UBOOT正式启动。
这里的分析没有涉及到坏块处理,ECC校验的部分,一方面现在太忙没时间细看。另一方面UBL不是重点,要想深究这个东西,还是到Linux内核里的 NAND驱动中深究,这里能用就行了。还有就是一开始担心的是我们项目用到的NAND是512B每页的老式NAND,担心UBL不支持,需要修改UBL源 码。后来读了UBL源码以后,特别是平台相关的device_nand.c文件,才发现TI的工程师已经把该考虑的都考虑的,都能用,老外就是牛X。不像 国内的代码,根本没有普适性和跨平台性。UBL整个源码,架构较清晰,注释也多,代码整体很完整和美观,很有参考和学习价值。读了UBL源码后,其实很容 易就能读懂NANDEraser,NANDWriter等的源码了,说白了就是平台初始化加NAND读写,呵呵。