After the board is powered on, it will start executing from here, mainly completing b
asic initialization, judging whether to start from NOR or NAND, and then moving the program to
SDRAM. After the transfer is successful, it will jump to the main function for execution.
Let's start looking at its specific code now!
The functions of GET and INCLUDE are the same, both of which are to import some compiled files.
GET option.inc
GET memcfg.inc
GET 2440addr.inc
Define SDRAM to work in Reflesh mode. SDRAM has two refresh modes: selfreflesh and autoreflesh. The latter is set during its use.
BIT_SELFREFRESH EQU (1<<22)
The following is the assignment of constants corresponding to the ARM processor mode register. The ARM processor has a CPSR register, and its last five bits determine which mode the processor is in. It can be seen that the definition of constants will not exceed the last 5 bits.
USERMODE EQU 0x10
FIQMODE EQU 0x11
IRQMODE EQU 0x12
SVCMODE EQU 0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
MODEMASK EQU 0x1f
NOINT EQU 0xc0
Stacks for each exception mode
UserStack EQU (_STACK_BASEADDRESS-0x3800) ;0x33ff4800 ~
SVCStack EQU (_STACK_BASEADDRESS-0x2800) ;0x33ff5800 ~
UndefStack EQU (_STACK_BASEADDRESS-0x2400) ;0x33ff5c00 ~
AbortStack EQU (_STACK_BASEADDRESS-0x2000) ;0x33ff6000 ~
IRQStack EQU (_STACK_BASEADDRESS-0x1000) ;0x33ff7000 ~
FIQStack EQU (_STACK_BASEADDRESS-0x0) ;0x33ff8000 ~
This section unifies the working state of arm and the corresponding software compilation method (the 16-bit compilation environment uses tasm.exe to compile). The working state of arm processor is divided into two types: 32-bit, arm executes word-aligned arm instruction set; 16-bit, arm executes half-word-aligned Thumb instruction set. Different working states have different compilation methods. So the following program is to determine the working mode of arm to determine its compilation method.
GBLL THUMBCODE //Define THUMBCODE This variable GBLL declares a global logic variable and initializes it to {FALSE}
[{CONFIG} = 16//"[" means "if", "|" means "else", "]" means "endif", CONFIG is an internal variable defined in
ADS compilation.
THUMBCODE SETL {TRUE}
CODE32
|
THUMBCODE SETL {FALSE}
]//If ARM is in 16-bit working state, set the global variable THUMBCODE to true.
MACRO//This is the keyword for macro definition
MOV_PC_LR //The function is to return from the subroutine
[ THUMBCODE
bx lr //When the target program is Thumb, use BX to jump back and switch the mode.
|
mov pc,lr //The target program is ARM instruction set, just assign lr to pc.
]
MEND //End mark of macro definition.
MACRO
MOVEQ_PC_LR //This is a subroutine return with an "equal" condition. Similar to what is mentioned above.
[ THUMBCODE
bxeq lr
|
moveq pc,lr
]
MEND
The handlexxx HANDLER handlexxx under the macro definition will be expanded into the following program segment. This program mainly transmits the entry address of the interrupt service program to the pc. The program uses 34 words to store the entry address of the interrupt service program. Each word space will have a label starting with handlerxxx.
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4 // Reserve space first to store the jump address.
stmfd sp!,{r0} //Push the working register into the stack.
ldr r0,=$HandleLabel
ldr r0,[r0] //The function of these two sentences is to put the entry address of the interrupt program in the intermediate variable r0 first.
str r0,[sp,#4]//Push the entry address of the interrupt service program into the stack.
ldmfd sp!,{r0,pc}//Finally, the interrupt program entry address in the stack is popped to the pc register, so that the corresponding interrupt service program can be executed.
MEND
S3C2440 has two interrupt modes: one with an interrupt vector table and one without. The one with a table has better real-time performance. When an external interrupt 0 occurs, the program automatically jumps to address 0x20. The instruction at address 0x20 is "ldr pc, = HandlerEINT0", so the program jumps to HandlerEINT0 to execute this macro operation, which is to assign the external interrupt address to PC.
An arm program is composed of three segments: R0, RW, and ZI. R0 is the code segment, RW is an initialized global variable, and ZI is an uninitialized global variable. BOOTLOADER needs to copy the RW segment to RAM and clear the ZI segment.
The compiler uses the following segments to record the start and end addresses of each segment:
|Image$$RO$$Base| ; RO segment start address|Image$$RO$$Limit| ; RO segment end address plus 1|Image$$RW$$Base| ; RW segment start address
|Image$$RW$$Limit| ; RW segment end address plus 1|Image$$ZI$$Base| ; ZI segment start address|Image$$ZI$$Limit| ; ZI segment end address plus 1
The values of these labels are determined by the compiler settings, such as the settings of ro-base and rw-base in the compiler software, for example, ro-base=0xc000000 rw-base=0xc5f0000. Here, the IMPORT pseudo-instruction (same as extren in C language) is used to introduce |Image$$RO$$Base|, |Image$$RO$$Limit|... and other weird variables are generated by the compiler. The three segments RO, RW, and ZI are all stored in Flash, but the addresses of RW and ZI in Flash are definitely not the locations where the variables are stored when the program is running. Therefore, when our program is initialized, RW and ZI in Flash should be copied to the corresponding locations in RAM. These variables are set by RO Base and RW Base set in the project settings of ADS, and are finally imported into the program by the compilation script and linker.
IMPORT |Image$$RO$$Base|
IMPORT |Image$$RO$$Limit|
IMPORT |Image$$RW$$Base|
IMPORT |Image$$ZI$$Base|
IMPORT |Image$$ZI$$Limit|
Introduce two variables of external variable mmu fast bus mode and synchronous bus mode
IMPORT MMU_SetAsyncBusMode
IMPORT MMU_SetFastBusMode
The main function we are familiar with
IMPORT Main
Function to copy image from Nandflash to SDRAM
IMPORT RdNF2SDRAM
Define the arm assembly program segment, the segment name is init segment, which is a read-only segment
AREA Init,CODE,READONLY
ENTRY
EXPORT __ENTRY //Export __ENTRY label
__ENTRY
ResetEntry
ASSERT :DEF:ENDIAN_CHANGE//Judge whether the mode change has been defined (ASSERT is a pseudo instruction, :DEF:lable judges whether the label has been defined)
[ ENDIAN_CHANGE
ASSERT :DEF:ENTRY_BUS_WIDTH//Judge whether the bus width is defined
[ENTRY_BUS_WIDTH=32//If the memory is 32-bit bus width
b ChangeBigEndian ;DCD 0xea000007
]
[ENTRY_BUS_WIDTH=16 //If the memory is 16-bit bus width
andeq r14,r7,r0,lsl #20 ;DCD 0x0007ea00
]
[ENTRY_BUS_WIDTH=8//If the memory is 8-bit bus width
streq r0,[r0,-r10,ror #1] ;DCD 0x070000ea
]
|//If the bus width is not defined, jump directly to the reset interrupt
b ResetHandler // Jump instruction executed by the program
]
b HandlerUndef ;handler for Undefined mode
b HandlerSWI ;handler for SWI interrupt
b HandlerPabort ;handler for PAbort
b HandlerDabort ;handler for DAbort
b . ;reserved
b HandlerIRQ ;handler for IRQ interrupt
b HandlerFIQ ;handler for FIQ interrupt
;@0x20
b EnterPWDN ; Must be @0x20. //Enter powerdown mode
The above 8 jump instructions are 8 exception interrupt processing vectors, which must be arranged in sequence. As far as I know, each time an exception occurs, the hardware will look up the table by itself.
HandlerFIQ HANDLER HandleFIQ
HandlerIRQ HANDLER HandleIRQ
HandlerUndef HANDLER HandlerUndef
HandlerSWI HANDLER HandleSWI
HandlerDabort TRADE HandlerDabort
HandlerPassport TRADE HandlerPassport
The following program is very important, it is the program to implement the second table lookup. ARM classifies all interrupts as an IRQ and a FIRQ interrupt exception. In order to know the specific interrupt, we can jump to the interrupt service routine corresponding to the interrupt.
IsrIRQ
sub sp,sp,#4 //Retain the value of the pc register
stmfd sp!,{r8-r9}//Push r8 r9 into the stack
ldr r9,=INTOFFSET//Load the address of interrupt offset INTOFFSET into r9
ldr r9,[r9]//Get the value in INTOFFSET unit to r9
ldr r8,=HandleEINT0 //The entry address of the vector table is assigned to r8
add r8,r8,r9,lsl #2//Find the address of the specific interrupt vector
ldr r8,[r8]//The entry address of the interrupt service routine stored in the interrupt vector is assigned to r8
str r8,[sp,#8]//Push into the stack
ldmfd sp!,{r8-r9,pc}//Stack pops up and jumps to the corresponding interrupt service routine
LTORG//Declaration text pool
After the board is powered on, the program executes b ResetHandler at 0x00
ResetHandler
ldr r0,=WTCON //turn off the watchdog
ldr r1,=0x0
str r1,[r0]
ldr r0,=INTMSK
ldr r1,=0xffffffff //Disable all interrupts
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x7fff //Disable all sub-interrupts
str r1,[r0]
[ {FALSE}
;rGPFDAT = (rGPFDAT & ~(0xf<<4)) | ((~data & 0xf)<<4);
; Led_Display
ldr r0,=GPBCON
ldr r1,=0x155500
str r1,[r0]//Make GPB10~GPB4 the output port and GPB3~GPB0 the input port
ldr r0,=GPBDAT
ldr r1,=0x0
str r1,[r0]//Make GPB10~GPB4 output low level, GPB3~GPB0 input low level
]
It can be found from the data sheet that when the output is 1, the LED is off, and vice versa.
LOCKTIME is the lock time counter of the pll. To reduce the lock time of the pll, adjust the LOCKTIME register.
ldr r0,=LOCKTIME
ldr r1,=0xffffff //After assigning this value, the locktime values of UPLL and MPLL will be set. You can ask Samsung why this value is set. I don't know much about it.
str r1,[r0]
At this point, you may not understand it very well. Let me explain it in detail here. This involves the knowledge of the clock module of arm9. arm9 has a clock control logic, which can generate the FCLK clock of the CPU, the HCLK clock of the AHB bus peripheral interface device, and the PCLK clock of the APB bus peripheral interface device. arm9 has two phase-locked loops PLL, one for FCLK, HCLK, and HCLK. One for the USB module. We call these two PLLs MPLL and UPLL respectively. After the system is reset, the PLL operates according to the default configuration. Since it is considered to be in an unstable state at this time, the external clock is used as the output of the FCLK clock. Only when the corresponding value is set to the PLLCON register, the PLL will run according to the frequency set by the software. At this time, the output of the PLL is used as FCLK. For FCLK, there are two different clocks as inputs in succession, so there is an adaptation time. The setting of this time is the constant we set in the LOCKTIME register here.
[PLL_ON_START//Set the CLKDIVN value to be valid after the PLL lock time.
ldr r0,=CLKDIVN
ldr r1,=CLKDIV_VAL ; 0=1:1:1, 1=1:1:2, 2=1:2:2, 3=1:2:4, 4=1:4:4, 5=1:4:8, 6=1:3:3, 7=1:3:6.
str r1,[r0]
It can be seen that the ratio of FCLK, PCLK and HCLK is set. The required ratio can be obtained by simply operating CLKDIVN.
[CLKDIV_VAL>1 //If Fclk:Hclk is not 1:1, execute the following
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000;R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
|
mrc p15,0,r0,c1,c0,0
bic r0,r0,#0xc0000000;R1_iA:OR:R1_nF
mcr p15,0,r0,c1,c0,0
]
It can be seen here that if the ratio of FCLK:HCLK is not 1:1, it is necessary to switch to asynchronous bus mode. On the contrary, if it is this ratio, it is necessary to switch to fast bus mode.
ldr r0,=UPLLCON //Configure UPLL
ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)//Here is the very familiar PMS, Fin = 12.0MHz, UCLK = 48MHz
str r1,[r0]
nop ; Caution: After UPLL setting, at least 7-clocks delay must be inserted for setting hardware be completed.
nop
nop
nop
nop
nop
nop
ldr r0,=MPLLCON //Configure MPLL
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;Fin = 12.0MHz, FCLK = 400MHz
str r1,[r0]
]
ldr r1,=GSTATUS2
ldr r0,[r1]
tst r0,#0x2
To determine whether the device is awakened from sleep mode, the detection of GSTATUS2[2] can determine whether the device is awakened from sleep mode.
bne WAKEUP_SLEEP // Jump if yes.
EXPORT StartPointAfterSleepWakeUp //Define an external StartPointAfterSleepWakeUp
StartPointAfterSleepWakeUp
adrl r0, SMRDATA
ldr r1,=BWSCON
add r2, r0, #52
0
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne %B0
The purpose of this code is to set up the storage controller. There is a SMRDATA data area behind the code, with r0 used to define its start address and r2 used to define its end address. r3 represents the 13 storage controllers. The code is obvious, it is to assign the memory data to the 13 storage controllers.
ldr r0,=GPFCON
ldr r1,=0x0
str r1,[r0]//set GPF as input function
ldr r0,=GPFUP
ldr r1,=0xff
str r1,[r0]//Disable pull-up resistor
ldr r1,=GPFDAT
ldr r0,[r1]
bic r0,r0,#(0x1e<<1) //bic is the bitwise AND of r0 and #(0x1e<<1).
tst r0,#0x1//Here is to test whether the last bit is 0. If it is 0, it means a key is pressed.
bne %F1 //When key 0 is not pressed, jump.
This code detects whether EINT0 is pressed.
ldr r0,=GPFCON
ldr r1,=0x55aa
str r1,[r0]//GPF7~GPF4 are set as output, GPF3~GPF0 are set as EINT0~EINT3
ldr r0,=GPFDAT
ldr r1,=0x0
str r1,[r0] //Obviously, GPF7~GPF4 are set to control the LED lights, and all low level lights are on. It serves as an indication.
mov r1,#0
move r2, #0
move r3, #0
mov r4,#0
move r5, #0
mov r6,#0
mov r7,#0
rice r8, #0
ldr r9,=0x4000000 ;64MB
ldr r0,=0x30000000
0
stmia r0!,{r1-r8}
subs r9,r9,#32
bne %B0
It is obvious that the program uses registers r1~r8 to clear all the memory from 0x30000000 to 0x34000000.
1
bl InitStacks // Initialize stack
ldr r0, =BWSCON
ldr r0, [r0]
ands r0, r0, #6 //OM[1:0] != 0, boot from NOR FLash or memory, no need to read NAND FLASH
bne copy_proc_beg //No need to boot from NAND FLASH, just jump here
adr r0, ResetEntry//OM[1:0] == 0, boot from NAND FLash
cmp r0, #0//Compare whether the entry address is at 0, if not, use the emulator
bne copy_proc_beg // The emulator does not need to be started in NAND FLASH
nand_boot_beg
[ {TRUE}
bl RdNF2SDRAM
]
ldr pc, =copy_proc_beg
Let's take a look at how RdNF2SDRAM works. The function of this code is to read the program from NAND into RAM.
void RdNF2SDRAM( )
{
U32 i;
U32 start_addr = 0x0;
unsigned char * to = (unsigned char *)0x30000000;
U32 size = 0x100000; //It can be calculated that the size is 8M.
rNF_Init(); //Let's take a closer look at this function.
as follows:
static void rNF_Init(void)
{
r
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(0<<0); //TACLS=1,TWRPH0=4,TWRPH1=0 initialize ECC, set CLE&ALE duration, set TWRPH0 and TWRPH1 duration.
rNFCONT = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<1)|(1<<0);//Before reading and writing NANDFLASH, the settings of bits 6, 5, and 4 ensure that ECC can be used; clear bit 13 to allow writing, erasing, and reading the contents of the area 0x4E000038~0x4E00003C; since we do not impose any restrictions on the reading and writing of this range, we do not need to set an interrupt to notify the system that the area in this range has been read or written, that is, bit 10 is cleared; RnB indicates whether the memory is currently busy. When bit 9 is set to 1, it means that an interrupt can be used to notify the CPU of the current memory status, and bit 8 is set to indicate whether it is a rising edge trigger or a falling edge trigger.
rNFSTAT = 0;
rNF_Reset();
}
Let's take a look at the specific code of rNF_Reset(). The code is as follows:
static void rNF_Reset()
{
NF_CE_L();
NF_CLEAR_RB();
NF_CMD(CMD_RESET);
NF_DETECT_RB();
NF_CE_H();
}
The code looks annoying, but it's not. It's just a bunch of macro definitions. Let me translate it directly. The translation is as follows:
rNFCONT &= ~(1<<1); // Bit 1 is cleared to indicate chip select is enabled, so the chip can work.
rNFSTAT |= (1<<2); // Clear bit 2. There is no need to determine whether the chip is busy.
rNFCMD = (CMD_RESET);//其中CMD_RESET=0xff。
while(!(rNFSTAT&(1<<2))); //When RnB changes from low level to high level, it will jump out of this loop. It is waiting for the NANDFLASH operation to be completed.
rNFCONT |= (1<<1); //Stop the chip from working.
In this way, the initialization of NANDFLASH is finally completed. Now we return to RdNF2SDRAM and continue the analysis.
switch(rNF_ReadID()) Let's analyze this function. The code is as follows:
static char rNF_ReadID()
{
char pMID;
char pDID;
char nBuff;
char n4thcycle;
int i;
NF_nFCE_L(); //Enable the chip to work
NF_CLEAR_RB(); //Clear bit 2 of NFSTAT to determine whether the film has completed the work.
NF_CMD(CMD_READID); //Send the read ID command to NFCMD.
NF_ADDR(0x0); //Send address to NFADDR
for ( i = 0; i < 100; i++ );
pMID = NF_RDDATA8();
pDID = NF_RDDATA8();
nBuff = NF_RDDATA8();
n4thcycle = NF_RDDATA8();
NF_nFCE_H();
return (pDID);
}//Why does pDID have other values in the final return? I don't quite understand. Let's return to the main program and have a look.
switch(rNF_ReadID())
{
case 0x76:
for(i = (start_addr >> 9); size > 0; ) // In this case, the size of a page is considered to be 512 bytes
{
rSB_ReadPage(i, to);
size -= 512;
to += 512;
i ++;
}
break;
case 0xf1:
case 0xda:
case 0xdc:
case 0xd3:
for(i = (start_addr >> 11); size > 0; ) // In this case, 2048 bytes are considered as one page
{
rLB_ReadPage(i, to);
size -= 2048;
to += 2048;
i ++;
}
break;
}
}
In fact, the contents of the second page of NANDFLASH are stored in a pointer array, and the starting address of this pointer array is 0x30000000. This is the to[i] array we will see below. The following two functions have the same function, but the difference is how big a page is, 512 or 2048.
static void rSB_ReadPage(U32 addr, unsigned char * to)
{
U32 i;
rNF_Reset();
// Enable the chip
NF_nFCE_L();
NF_CLEAR_RB();
// Issue Read command
NF_CMD(CMD_READ);
// Set up address
NF_ADDR(0x00);
NF_ADDR((addr) & 0xff);
NF_ADDR((addr >> 8) & 0xff);
NF_ADDR((addr >> 16) & 0xff);
NF_DETECT_RB(); // wait tR(max 12us)
for (i = 0; i < 512; i++)
{
to[i] = NF_RDDATA8();
}
NF_nFCE_H();
}
static void rLB_ReadPage(U32 addr, unsigned char * to)
{
U32 i;
rNF_Reset();
// Enable the chip
NF_nFCE_L();
NF_CLEAR_RB();
// Issue Read command
NF_CMD(CMD_READ);
// Set up address
NF_ADDR(0x00);
NF_ADDR(0x00);
NF_ADDR((addr) & 0xff);
NF_ADDR((addr >> 8) & 0xff);
NF_ADDR((addr >> 16) & 0xff);
NF_CMD(CMD_READ3);
NF_DETECT_RB(); // wait tR(max 12us)
for (i = 0; i < 2048; i++)
{
to[i] = NF_RDDATA8();
}
NF_nFCE_H();
}
It can be seen that they are all reset at the beginning. The difference lies in how the incoming address is converted and then paid to the NFADDR register each time. The specific method depends on the NAND data manual.
Let's go back to the 2440init.s program, and then there is the following sentence:
ldr pc, =copy_proc_beg
We have seen the label copy_proc_beg appear many times before. The function of the code below this label is to copy the contents of nand flash to ram.
copy_proc_beg
adr r0, ResetEntry
ldr r2, BaseOfROM
cmp r0, r2 // compare the two
ldreq r0, TopOfROM //If they are the same, assign the end position of R0 to r0, which is also the starting position of RW.
beq InitRam //If they are the same, jump to the position of this label.
ldr r3, TopOfROM//The following code is for the copy method when the code is in NOR FLASH.
0
ldmia r0!, {r4-r7}
stmia r2!, {r4-r7}
cmp r2, r3
bcc %B0 //The function of these codes is to move the contents of ResetEntry to BaseOfROM (the starting position of R0, which is declared later).
sub r2, r2, r3
sub r0, r0, r2 //Here, the position of ResetEntry is moved down to prepare for the subsequent data copy.
InitRam
ldr r2, BaseOfBSS
ldr r3, BaseOfZero
0
cmp r2, r3
ldrcc r1, [r0], #4
strcc r1, [r2], #4
bcc %B0 //It can be seen that this section copies the data defined in ResetEntry to the RW segment.
mov r0, #0
ldr r3, EndOfBSS
1
cmp r2, r3
strcc r0, [r2], #4
bcc %B1 //If there is extra space left after copying the data, fill it with 0
ldr pc, =%F2 ;goto compiler address
2
ldr r0,=HandleIRQ
ldr r1,=IsrIRQ
str r1,[r0]//These three statements clearly show that the storage unit of the HandleIRQ interrupt vector is assigned the address of the IsrIRQ label, so that when an IRQ interrupt occurs, it will go directly to the secondary table to confirm which specific interrupt occurred.
[ :LNOT:THUMBCODE
bl Main //At this point, we see that we have entered the MAIN function.
b .
]
[ THUMBCODE ;for start-up code for Thumb mode
orr lr,pc,#1
bx lr
CODE16
bl Main //You can see that the above code indicates that if arm is in THUMBCODE instruction mode, the mode will be switched.
b .
CODE32
]
So far, we have analyzed the startup code of 2440init.s. If there are any errors, please point them out! Thank you!