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的擦除烧写源码,串口烧写源码等(flash_utils\DM36x\CCS下包括NANDWriter和UBL两个文件夹,分别对应针对IPNC的CCS的project)。下面只分析UBL的启动代码。
入门代码是汇编文件start.S,主要是切换操作模式,建立堆栈等,然后跳转到main函数,进入到
board_utilities/flash_utils/
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;
/*
UBL结束, gEntryPoint将u-boot入口
交给 APPEntry
*/
( * APPEntry) ( ) ; }
U-boot一般存在于DEVICE_NAND_UBL_SEARCH_START_BLOCK以后块的第0页,ubl试着从 DEVICE_NAND_UBL_SEARCH_START_BLOCK块向后搜索每块的第0页,找到后,前24个字节分别记录着u-boot描述,如入口函数等
//DM36x/CCS/NANDWriter/src/nandwriter.c nandwriter()函数中
// Fill in NandBoot header,rxBuf为刚搜到的页 gNandBoot. entryPoint = * ( ( ( Uint32 * ) ( & rxBuf[ 4] ) ) ) ; /* The first "long" is entry point for Application ,u-boot主程序将从这里开始执行*/ gNandBoot. numPage = * ( ( ( Uint32 * ) ( & rxBuf[ 8] ) ) ) ; /* The second "long" is the number of pages ,代码总页数,有多少数据要载入到DDR*/ gNandBoot. block = * ( ( ( Uint32 * ) ( & rxBuf[ 12] ) ) ) ; /* The third "long" is the block where Application is stored in NAND */ gNandBoot. page = * ( ( ( Uint32 * ) ( & rxBuf[ 16] ) ) ) ; /* The fourth "long" is the page number where Application is stored in NAND */ gNandBoot. ldAddress = * ( ( ( Uint32 * ) ( & rxBuf[ 20] ) ) ) ; /* The fifth "long" is the Application load address ,一般情况,程序放在这个地址开始加载*/
根据以上信息一次读入每一页,然后将u-boot入口传递给gEntryPoint。
main函数主要调用了LOCAL_boot函数来进行实质的引导功能,下面是此函数的内容:
static Uint32 LOCAL_boot( void ) { DEVICE_BootMode bootMode; // Read boot mode
从BOOTCFG系统寄存器读出,得到目前BOOTSEL[0..2]
bootMode = DEVICE_bootMode( ) ;
if ( bootMode = = DEVICE_BOOTMODE_UART) { // Wait until the RBL is done using the UART.
//对于通过UART启动的,这些代码应该是存在于uart设备上
while ( ( UART0- > LSR & 0x40) = = 0 ) ; } // Platform Initialization if ( DEVICE_init( ) ! = E_PASS ) /*
DEVICE_init函数来进行平台的最底层初始化,包括电源域,时钟,DDR,EMIF,UART,I2C,TIMER等 */ { 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) ; /*
对全局变量currMemPtr赋值 */ // 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
//
NANDBOOT_copy()首先打开 NandFlash设备,将NandFlash的硬件信息放在hNandInfo数据结构内。 移植ubl时需要修改与hNandInfo数据结构相关的参数,比如EMIF地址(EMIFStart = 0x02000000 在ubl.cmd;) DEVICE_NAND_CHIP_infoTable结构中的页大小、块数、块中的页数、...
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,Y0-Y7全部作为video in(不作为GPIO),GIO43作为SD1的clk,McBsp开启,MMCSD0关闭 DEVICE_pinmuxControl( 1, 0xFFFFFFFF, 0x00145555) ; // All Video Outputs,视频Cout0-Cout7作为色度信号输出使能,场消隐/行消隐同步信号使能,LCD的OE功能关闭 DEVICE_pinmuxControl( 2, 0xFFFFFFFF, 0x000000DA) ; // EMIFA,总线使能,但是CE0没有使能,0xDA可能在合众达板子上运行有问题,因为他将ce0设置成GPIO,这样nandflash失效了,我想可能是TI原版有个cpld,在合众达的测试程序,值为0x55。 DEVICE_pinmuxControl( 3, 0xFFFFFFFF, 0x00180000) ; // SPI0, SPI1, UART1, I2C, SD0, SD1, McBSP0, CLKOUTs,串口1使能,其他均作为GPIO,网卡没使能 DEVICE_pinmuxControl( 4, 0xFFFFFFFF, 0x55555555) ; //SI1-SPI4使能,MMCSD1 使能
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函数决定复用引脚的功能选择,详见数据手册查看引脚功能。
接着调用DM36x/common/src/device.c下的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; }
Uint32 DEVICE_PLL2Init() {
...
// Post divider setting for PLL2 PLL2- > PLLDIV2 = 0x8001; // 594/2 =297 Mhz -> ARM PLL2- > PLLDIV4 = 0x801C; // POST DIV 594/29 = 20.48 -> VOICE PLL2- > PLLDIV5 = 0x8007;
...
}
继续在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函数了