本文轉載自:http://blog.csdn.net/eshing/article/details/37116637linux
上一章咱們講解了如何對代碼進行重定位,可是將代碼重定位到只有256K IRAM中做用不大。app
正確的作法是將代碼重定位到容量更大的主存中,即DRAM。Exynos4412中有兩個獨立的DRAM控制器,分別叫DMC0和DMC1。DMC0和DMC1分別支持最大1.5G的DRAM,它們都支持DDR2/DDR3和LPDDR2等,512 Mb, 1 Gb, 2 Gb, 4 Gb and 8 Gbit的內存設備,支持16/32bit的位寬。DRAM0 對應的地址是0x4000_0000~0xAFFF_FFF共1.5GB,DRAM1 對應的地址是0xA000_000~0x0000_0000共1.5GB。less
圖7-一、DRAM控制地址圖ide
查閱Tiny4412的原理圖:函數
圖7-二、DRAM電路原理樣圖學習
Tiny4412的512M的DRAM是由4片大小爲128M的DDR3芯片組合而成(上圖僅爲其中一片),觀察片選引腳可知4片DRAM芯片都是掛接到DMC0處。ui
如何才能使用DRAM?對應Tiny4412而言,因爲它只用到了DMC0,全部咱們只須要初始化DMC0和DDR3 DRAM芯片便可,本實驗全部的初始化代碼所有來自於U-BOOT程序。須要說明一點的,我在寫這個裸機程序文檔以前,已經看了一段時間U-BOOT程序,U-BOOT程序中對DRAM也作了相應的初始化工做,我就直接拿來用了,在參考學習U-BOOT過程當中,我也記錄了一份學習文檔,裏面有比較詳細的DRAM初始化說明,等我U-BOOT實驗成功後,我會整理一份相應文檔。this
完整代碼見目錄6_sdram,與前一章的代碼相比,本章的代碼有了較大的修改。首先整個工程.net
分爲BL2和USER兩個目錄,目錄BL2下的代碼會被編譯連接成一個名爲BL2.bin的文件,而目錄user下的代碼會被編譯連接成一個名爲user_bin.bin的文件。其中,BL2.bin文件的連接地址是0(使用的是位置無關碼,程序能夠在任意可用的內存中運行),user_bin.bin 文件的連接地址是0x43E00000(使用的並非位置無關碼,全部程序必須位於該地址處才能正常運行)。BL2.bin需被燒寫到sd卡的扇區17,user_bin.bin需被燒寫到sd卡的扇區49處,BL1去哪了?這個我沒有寫,仍是用了U-Boot裏找來的由三星提供的E4412_N.bl1.bin,燒寫的SD卡扇區就是Seek1,爲何要這樣燒寫的緣由將在後面的程序講解中給出,完整的燒寫過程可參考本章第三節的燒寫步驟。整個程序的運行過程大體以下:系統上電後,首先將sd卡扇區1處的E4412_N.bl1.bin拷貝到IRAM的0x02020000地址處,而後運行該部分代碼,該部分代碼首先又會加載BL2.bin,BL2.bin會進行時鐘和DRAM初始化,而後把位於sd卡中扇區49處的user_bin.bin拷貝到DRAM的0x43E00000地址處,最後跳轉到該地址處繼續運行。翻譯
相比上一章的代碼,本章的start.S在多了以下三個步驟:
第一步 調用system_clock_init函數初始化時鐘,函數的實現位於clock_init_tiny4412.S,這個文件是從U-BOOT中Copy過來進行了適當修改獲得的。
第二步 調用mem_ctrl_asm_init函數初始化內存,函數的實現位於mem_init_tiny4412.S,這個文件是從U-BOOT中Copy過來進行了適當修改獲得的。
第三步 調用uart_asm_init函數初始化串口0,函數的實現位於mem_init_tiny4412.S,這個文件是從U-BOOT中Copy過來的。我copy了這個函數是爲了在調試時使用的,說實話,這個程序有時是不能正常運行歷功了,我如今也沒有找到緣由所在,我如今主要以爲內存初始化有問題,以及下一步的拷貝函數有問題。
第四步 調用copy_code_to_dram將user_bin.bin從SD 卡拷貝到DRAM 的0x43E00000 處,
copy_code_to_dram的實現位於文件mmc_relocate.c;
clock_init_tiny4412.S是從從U-BOOT中Copy過來的,我進行了一點點修改,我之因此把這個文件也Copy過來是由於我以爲我將要Copy的mem_init_tiny4412.S中mem_ctrl_asm_init初始化函數,與時鐘相關的寄存器設置是創建在U-BOOT的時鐘基礎上,若是不要clock_init_tiny4412.S文件,DRAM可能初始化不成功。
關於這個文件的解釋,你們能夠參考我隨後的U-BOOT的移植說明文檔。這裏因爲設置的寄存器過多且繁雜,不作過多說明。
Exynos4412已經告訴咱們如何初始化DDR2類型的DRAM,主要分爲初始化PHY DLL、初始化控制寄存器,和初始化DRAM 三大步驟,具體細分共21個小步驟,至關繁瑣,因爲涉及到的寄存器過多,若是一一介紹將須要很是大的篇幅,全部這個文檔並不會一一解釋寄存器的設置,DRAM的簡要初始化步驟見下圖7-3,此圖是Copy於芯片手冊。
圖7-三、DRAM初始化簡要過程
如今開始關注一下芯片手冊上關於DDR3的初始化流程,找到手冊 1046頁,能夠看到其有一段關於LPDDR2-S4的初始化步驟,LPDDR2表示低功耗DDR2,DDR3的初始化過程應和這個差很少,咱們就按這個過程來初始化DDR3:
18.3.1 LPDDR2-S4
Use the sequence given here to initialize LPDDR2 devices. Unlessspecified otherwise, these steps are
mandatory. Note that the memory CK/CKn must be less than or equal to50 MHz before you initialize the
LPDDR2-S4 device.
1. DMC must assert and holdCKE to a logic low level to provide stable power for memory device and thenapply
stable clock.
2. Set thePhyControl0.ctrl_start_point and PhyControl0.ctrl_inc bit-fields to a correctvalue according to clock
frequency. Set the PhyControl0.ctrl_dll_on bit-field to"1" to activate the PHY DLL.
3. DQS cleaning: Set thePhyControl1.ctrl_shiftc and PhyControl1.ctrl_offsetc bit-fields to theappropriate value
according to clock frequency, board delay, and memory tDQSCKparameter.
4. Set thePhyControl0.ctrl_start bit-field to "1".
5. Set the ConControl. Atthis moment, an auto-refresh counter should be disabled.
6. Set the MemControl. Atthis moment, all power down modes should be disabled.
7. Set the MemConfig0register. When there are two external memory chips, set the MemConfig1register.
8. Set the PrechConfig andPwrdnConfig registers.
9. Set the TimingAref,TimingRow, TimingData, and TimingPower registers according to memory AC
parameters.
10. Set the QosControl0 to 15and QosConfig0 to 15 registers when a certain bus master requires QoS scheme.
11. Wait for thePhyStatus0.ctrl_locked bit-fields to change to "1". Verify whetherPHY DLL is locked.
PHY DLL compensates the changes of delay amount that pressure,volume, and temperature variation
causes during memory operation. Therefore, you should not power offPHY DLL for reliable operation.
It can be in power-off mode except when it runs at low frequency.When you use the power-off mode,
set the PhyControl0.ctrl_force bit-field to the correct valueaccording to the PhyStatus0.ctrl_lock_value[9:2]
bit-field for fix delay amount. Clear the PhyControl0.ctrl_dll_onbit-field to turn off PHY DLL.
12. Set thePhyControl1.fp_resync bit-field to "1" to update DLL information.
13. Confirm that Clock Enable(CKE) is in a logic low level at least 100ns after power on.
14. Issue a NOP command byusing the DirectCmd register to assert and hold CKE to a logic high level.
15. Wait for a minimum of 200s.
16. Issue a MRS command byusing the DirectCmd register to reset memory devices and program the operating
parameters.
17. Wait for minimum of 1 s.
18. Issue a MRR command byusing the DirectCmd register to poll the DAI bit of the MRStatus register.
This is to know whether or not Device Auto-Initialization iscomplete.
19. If there are two externalmemory chips, execute step 14 to 19 for chip1 memory device.
20. Set the ConControl toturn on an auto-refresh counter.
21. Set MemControl registerwhen you require power-down modes.
翻譯上面的步驟:
一、DMC功能必須設置,而且要保持CKE爲低電平,以即可以提供穩定的電源和時鐘給DDR
二、根據時鐘頻率設置PhyControl0.ctrl_start_point 和PhyControl0.ctrl_inc bit-fields,而且設置PhyControl0.ctrl_dll_on bit-field 爲 "1" 已啓動PHY DLL。
三、DQS 清除,根據時鐘頻率、板子延時和芯片的tDQSCK參數設置PhyControl1.ctrl_shiftc 和PhyControl1.ctrl_offsetcbit-fields。
四、設置 PhyControl0.ctrl_start bit-field 爲"1"。
五、設置ConControl,此時,不能使能自動刷新計數器(auto-refresh counter )。
6. 設置MemControl,此時,全部的power down模式應關閉。
七、設置MemConfig0 寄存器,當外面有兩片存儲芯片時,設置MemConfig1寄存器。
八、設置 PrechConfig和 PwrdnConfig寄存器。
九、根據DDR3的 AC參數設置TimingAref, TimingRow, TimingData, 和 TimingPower寄存器。
十、當總線主控者須要QoS時序參數時,設置QosControl0 到15寄存器和QosConfig0 到 15寄存器。
十一、等待 PhyStatus0.ctrl_locked bit-fields 變成 "1",以肯定 PHY DLL 是否鎖定。PHY DLL 能夠補償壓力、體積?和溫度等環境的變化,由於在芯片工做期間,咱們不能關閉了PHY DLL的電源,只有當他在低的時鐘頻率時才能夠切換到Power-off 模式,當咱們用power-off 模式,參考PhyStatus0.ctrl_lock_value[9:2]的延時參數來設置PhyControl0.ctrl_forcebit-field,清楚PhyControl0.ctrl_dll_on bit-field來關閉 PHY DLL。
十二、設置PhyControl1.fp_resync bit-field 爲 "1" 來更新 DLL的設置。
1三、確保在電源上電後至少保持Clock Enable (CKE)在低電平100ns。
1四、用DirectCmd 寄存器來執行一條NOP指令且保持CKE 爲高電平。
1五、至少等待200us。
1六、發出MRS指令來從新設置存儲芯片的操做參數。
1七、至少等待1us。
1八、用MRR指令來查詢MRStatus的寄存器的DAI位,用這們來肯定自動初始化過程是否完成。
1九、若是外部有別的存儲芯片,重複執行14到19步來設置芯片1。
20、設置ConControl來啓動auto-refresh counter.
2一、當咱們要進行power-down模式,設置MemControl寄存器。
mem_init_tiny4412.S就是參考了上述的步驟進行內存的初始化,你們可參考上面步驟,對照代碼來進行查看,這裏不作詳細說明,有時候學東西就是知道如何查看設置過程就好,太多細節咱們是沒有時間來一一挖出來的,其實,這樣的代碼流程,芯片公司都應提供好,用戶頂多就是參考本身電路板進行必要參數修改,讓其能正常運行便可,若有自需,自已去深挖吧。
這個程序爲何這麼寫,我本身很差給你們說清楚了,由於我沒有辦法參考着《Linux平臺下Mini210S裸機程序開發指南》文檔從我現有資料中找到他所說的相似的芯片說明。我只能參考着他的說明,從U-BOOT中找到一個函數,從他那裏COPY了一些代碼,來實現這個函數。你們先來一遍《linux平臺下Mini210S裸機程序開發指南》文檔關於此函數的說明 ,我在說一下我是怎麼寫的這個函數的。
通過mem_init函數對DRAM的初始化後,咱們就能夠拷貝代碼到DRAM中而後跳轉到DRAM中繼續運行了,由BL1目錄下的mmc_relocate.c來實現這部分功能,mmc_relocate.c的代碼以下:
void copy_code_to_dram(void)
{
unsigned long ch;
void (*BL2)(void);
ch = *(volatile unsigned int *)(0xD0037488);
copy_sd_sd_to_mem copy_bl2 =(copy_sd_sd_to_mem) (*(unsigned int *) (0xD0037F98));
unsigned int ret;
// 通道0
if (ch == 0xEB000000)
{
// 0:channel 0
// 49:源,代碼位於扇區49,1 sector = 512 bytes
// 32:長度,拷貝32 sector,既16K
// 0x23E00000:目的,連接地址0x23E00000
ret = copy_bl2(0, 49, 32,(unsigned int*)0x23E00000, 0);
}
// 通道2
else if (ch == 0xEB200000)
{
ret = copy_bl2(2, 49, 32,(unsigned int*)0x23E00000, 0);
}
else
return;
// 跳轉到DRAM中
BL2 = (void *)0x23E00000;
(*BL2)();
}
首先咱們定義了一個函數指針copy_bl2,將其賦值爲0xD0037F98。爲何要這麼作,是由於IROM內部固化的代碼已經幫咱們實現了一類拷貝函數,其中就包括從sd卡拷貝內容到DRAM的函數,這類函數所位於的地址見下圖:
由上圖可知,External Copy Function位於0xD0037F80~0xD0038000處,其中sd卡拷貝內容到DRAM的函數就位於地址0xD0037F98,其函數原型以下:
其中:
StartBlkAddress:從第幾個扇區開始拷貝,一個扇區爲512byte
blockSize:拷貝多少個扇區
memoryPtr:拷貝到DRAM的哪一個地址上
with_init:是否須要初始化sd卡
有了上面這些知識,也就很容易看懂copy_code_to_dram 函數了。經過讀地址0xD0037488上的值來肯定是使用通道0仍是通道1,芯片手冊上明確指出「sd/MMC/eMMC boot – MMC Channel 0 is used for first boot. And Channel 2 is used forSecond boot」,咱們的BL1.bin就是first boot,因此會使用通道0,調用CopysdMMCtoMem函數將BL2.bin從sd卡的扇區49拷貝到DRAM的0x23E00000處,拷貝的長度是16K。最後給BL2這個函數指針賦值0x23E0000,而後調用BL2函數便可跳轉到0x23E0000處運行BL2.bin裏的代碼了。
你們看明白了,一句話三星的芯片內部已經有一個函數是用爲從外部存儲COPY東西內部iRAM或者DRAM中了,可是我在現有三星手冊上沒有找到相似的函數說明,沒辦法,咱們這些學習者,是很難拿全芯片廠商的資料的。但也不是就沒有辦法實現這個實驗了,U-BOOT中總會幹這麼個事吧,裏面確定有相關代碼,因此我只能去分析U-BOOT的代碼了,裏面有一個文件叫/arch/arm/cpu/armv7/exynos/irom_copy.c的文件,裏面的一個函數movi_uboot_copy(),就是將U-BOOT複製到DRAM中的,好了從這裏複製必要代碼實現本身的函數吧,咱們的函數主要內容以下:
下面內容是定義了咱們將要使用的COPY函數,這個函數就是實現了Mini210S中的copy_bl2的功能。
#defineISRAM_ADDRESS 0x02020000
#defineSECURE_CONTEXT_BASE 0x02023000
#defineEXTERNAL_FUNC_ADDRESS (ISRAM_ADDRESS +0x0030)
#defineEXT_eMMC43_BL2_ByCPU_ADDRESS (EXTERNAL_FUNC_ADDRESS+ 0x4)
#defineMSH_ReadFromFIFO_eMMC_ADDRESS (EXTERNAL_FUNC_ADDRESS+ 0x14)
#defineMSH_EndBootOp_eMMC_ADDRESS (EXTERNAL_FUNC_ADDRESS+ 0x18)
#defineLoadImageFromUsb_ADDRESS (EXTERNAL_FUNC_ADDRESS+ 0x40)
#defineSDMMC_ReadBlocks(uStartBlk, uNumOfBlks, uDstAddr)
\\定義COPY函數的原型。
(((void(*)(unsigned int, unsigned int,unsigned int*))(*((unsigned int *)EXTERNAL_FUNC_ADDRESS)))(uStartBlk,uNumOfBlks, uDstAddr))
下面開始複製代碼到DRAM中,首先聲明瞭兩個串口輸出函數,實現這兩個函數是爲了調試用的。由於我說過,我如今程序不必定能執行成功,有時LED燈閃爍了,有時又沒有。因此我打出了必要東西來調試用,你們也當學習嘛。
externvoid uart_asm_putc(int c);
externvoid uart_asm_putx(int x);
voidcopy_code_to_dram(void)
{
void (*user_bin)(void);
//這裏怕DRAM沒有初始化完成,等待了一會
volatile unsigned long count=0x100000;
while(count>0){
count--;}
uart_asm_putc('C');
uart_asm_putc('O');
uart_asm_putc('P');
uart_asm_putc('Y');
uart_asm_putc('\r');
uart_asm_putc('\n');
//從SD卡扇區49處複製32個扇區的內容到內存地址0x43e00000處
SDMMC_ReadBlocks(49,32,0x43e00000);
uart_asm_putc('O');
uart_asm_putc('V');
uart_asm_putc('E');
uart_asm_putc('R');
uart_asm_putc('\r');
uart_asm_putc('\n');
unsigned int *p;
int i;
//輸出內存地址0x40000000處50個字的內容
p = (unsigned int *) 0x40000000;
for (i = 0; i < 50; i++) {
uart_asm_putx(*(p+i));
uart_asm_putc(' ');
}
uart_asm_putc('\r');
uart_asm_putc('\n');
uart_asm_putc('\r');
uart_asm_putc('\n');
//輸出內存地址0x43dfffF0處100個字的內容
p = (unsigned int *) 0x43dfffF0;
for (i = 0; i < 100; i++) {
uart_asm_putx(*(p+i));
uart_asm_putc(' ');
}
uart_asm_putc('\r');
uart_asm_putc('\n');
uart_asm_putc('\r');
uart_asm_putc('\n');
//輸出內存地址0x43e00000處100個字的內容
p = (unsigned int *) 0x43e00000;
for (i = 0; i < 100; i++) {
uart_asm_putx(*(p+i));
uart_asm_putc(' ');
}
// 跳轉到DRAM處執行
user_bin = (void *)0x43e00000;
(*user_bin)();
}
Makefile的內容以下所示,其實總的內容仍是和之前幾章下的Makefile沒有太多區別,主要的區別是我將前面幾章中提到的sd_fuse目錄下的V310-EVT1-mkbl2.c放到了BL2目錄下,因此這裏在Makefile裏增長兩行話,給生成的u-boot.bin文件增長較驗和尾部。因此後面的燒寫操做也和前幾章的有一所不一樣了。
sdram.bin: start.o clock_init_tiny4412.o mem_init_tiny4412.ommc_relocate.o
arm-linux-ld-Tsdram.lds -o sdram.elf $^
arm-linux-objcopy-O binary sdram.elf u-boot.bin
arm-linux-objdump-D sdram.elf > sdram_elf.dis
gcc V310-EVT1-mkbl2.c -o mkbl2
./mkbl2 u-boot.bin bl2.bin 14336
%.o : %.S
arm-linux-gcc -o $@$< -c
%.o : %.c
arm-linux-gcc -o $@$< -c
clean:
rm *.o *.elf *.bin*.dis -f
SECTIONS
{
. = 0x43E00000;
.text : {
start.o
* (.text)
}
.bss : {
* (.bss)
}
.data : {
* (.data)
}
}
從上能夠看出user_bin.bin的連接地址是0x43E00000。
BL2.bin跳轉到0x43E00000後,實際上運行的就是USER/start.S裏的代碼,由於user_bin.bin的連接腳本sdram.lds裏指定了代碼段的最開始放的是start.o。在USER/start.S中只作了一件事,就是使用一條位置相關的指令:ldr PC, =main來調用main函數,main函數的做用與上一章的代碼同樣化,都是LED閃爍。
fast_fuse.sh主要的變更是增長了燒寫user_bin.bin的dd命令,以下所示,燒寫的位置是49.
uboot_position=49
#<user_bin fusing>
echo "---------------------------------------"
echo "user_bin fusing"
dd iflag=dsync oflag=dsync if=./USER/user_bin.bin of=$1seek=$uboot_position
已將SD卡插入電腦,假設linux識別了SD卡,其識別號爲sdb。執行下面命令:
# chmod 777 –R 6_sdram
# cd 6_sdram
# make
# ./ fast_fuse /dev/sdb
將sd卡插入Tiny4412中,選擇sd卡啓動,和電腦能過串口0鏈接好,打開一個串口調試助手,而後上電,能夠看到如下現象:
串口助手中會顯示一些信息,LED會正常閃爍,該現象與上一章的代碼運行效果如出一轍,可是程序的運行過程卻有了很大的區別。到此爲止,咱們就基本瞭解重定位的相關知識了。
若是沒有正常閃爍,你們能夠對比串口助手打印的0x43e00000位置處二制值指令是否和USER/ user_bin_elf.dis的反彙編指令同樣,若是不同就是問題所在,估計沒有拷貝成功代碼,緣由可能有時鐘、內存初始化不對,複製代碼函數有問題等。還有一個可能就是你的SD卡有問題,建議從新換一張來試試,我兩張SD卡,有一張時好時壞,我調試了好久也沒有找到緣由,後來我從新換了一張程序就能很正常的運行了。
按理說下面一章原本應參考着Mini210S的文檔,用Minitools進行實驗了,但當我用 Tiny4412的所MiniToolS來實驗裸機程序時,才發現不能像Mini210S說的那樣運行程序,我也沒有找到方法使MiniToolS能下載裸機程序,因此《Linux平臺下Mini210S裸機程序開發指南》後面關於使用Minitools的下載程序實驗我也沒有去深究了,若是誰找到方法,請分享了來吧。我急於去實現U-BOOT下LCD顯示功能,下一章就用本章的方法來實驗LCD裸機程序的實驗。爲何如今纔開始作LCD裸機程序,是因爲LCD顯示須要一塊大的Bufffer來存儲圖像數據,前面在Exynos4412芯片內部RAM是沒有這麼大的空間的,因此只能到如今,將外部DRAM初始化了,且能實現代碼的得定向了,就能爲所欲爲的運行一些稍大的程序了。