Nand flash驅動的編寫與移植
阿新 • • 發佈:2019-01-20
1 Nand flash工作原理
S3C2410板的Nand Flash支援由兩部分組成:Nand Flash控制器(整合在S3C2410 CPU)和Nand Flash儲存
晶片(K9F1208U0B)兩大部分組成。當要訪問Nand Flash中的資料時,必須通過Nand Flash控制器傳送命
令才能完成。所以, Nand Flash相當於S3C2410的一個外設,而不位於它的記憶體地址區.
1.1 Nand flash晶片工作原理
Nand flash晶片型號為Samsung K9F1208U0B,資料儲存容量為64MB,採用塊頁式儲存管理。8個I/O
引腳充當資料、地址、命令的複用埠。
1.1.1 晶片內部儲存佈局及儲存操作特點
一片Nand flash為一個裝置(device), 其資料儲存分層為:
1裝置(Device) = 4096 塊(Blocks)
1塊(Block) = 32頁/行(Pages/rows) ;頁與行是相同的意思,叫法不一樣
1塊(Page) = 528位元組(Bytes) = 資料塊大小(512Bytes) + OOB塊大小(16Bytes)
在每一頁中,最後16個位元組(又稱OOB)用於Nand Flash命令執行完後設置狀態用,剩餘512個位元組又
分為前半部分和後半部分。可以通過Nand Flash命令00h/01h/50h分別對前半部、後半部、OOB進行定位通過
Nand Flash內建的指標指向各自的首地址。
儲存操作特點:
1. 擦除操作的最小單位是塊。
2. Nand Flash晶片每一位(bit)只能從1變為0,而不能從0變為1,所以在對其進行寫入操作之前要一定將相應
塊擦除(擦除即是將相應塊得位全部變為1).
3. OOB部分的第六位元組(即517位元組)標誌是否是壞塊,如果不是壞塊該值為FF,否則為壞塊。
4. 除OOB第六位元組外,通常至少把OOB的前3個位元組存放Nand Flash硬體ECC碼(關於硬體ECC碼請參看
Nandflash 控制器一節).
1.1.2 重要晶片引腳功能
I/O0I/O7:複用引腳。可以通過它向nand flash晶片輸入資料、地址、nand flash命令以及輸出資料和操作
狀態資訊。
CLE(Command Latch Enable): 命令鎖存允許
ALE(Address Lactch Enable): 地址鎖存允許
CE: 晶片選擇
RE: 讀允許
WE: 寫允許
WP: 在寫或擦除期間,提供防寫
R/B: 讀/忙輸出
1.1.3 定址方式
Samsung K9F1208U0B Nand Flash 片內定址採用26位地址形式。從第0位開始分四次通過I/O0-I/O7進行
傳送,並進行片內定址。具體含義如下:
0-7位:位元組在上半部、下半部及OOB內的偏移地址
8位:值為0代表對一頁內前256個位元組進行定址
值為1代表對一頁內後256個位元組進行定址
9-13位:對頁進行定址 14-25位:對塊進行定址
當傳送地址時,從位0開始
1.1.4 Nand flash主要內設命令詳細介紹
Nand Flash命令執行是通過將命令字送到Nand Flash控制器的命令暫存器來執行。
Nand Flash的命令是分週期執行的,每條命令都有一個或多個執行週期,每個執行週期都有相映程式碼表示該周
期將要執行的動作。
主要命令有:Read 1、Read 2、Read ID、Reset、Page Program、Block Erase、Read Status。
詳細介紹如下:
1. Read 1:
功能:表示將要讀取Nand flash儲存空間中一個頁的前半部分,並且將內建指標定位到前半部分的第一個位元組。
命令程式碼:00h
2. Read 2:
功能:表示將要讀取Nand flash儲存空間中一個頁的後半部分,並且將內建指標定位到後半部分的第一個位元組。
命令程式碼:01h
3. Read ID:
功能:讀取Nand flash晶片的ID號
命令程式碼:90h
4. Reset:
功能:重啟晶片。
命令程式碼:FFh
5. Page Program:
功能:對頁進行程式設計命令, 用於寫操作。
命令程式碼:首先寫入00h(A區)/01h(B區)/05h(C區), 表示寫入那個區; 再寫入80h開始程式設計模式(寫入模式),接
下來寫入地址和資料; 最後寫入10h表示程式設計結束.
6. Block Erase
功能:塊擦除命令。
命令程式碼:首先寫入60h進入擦寫模式,然後輸入塊地址; 接下來寫入D0h, 表示擦寫結束.
7. Read Status
功能:讀取內部狀態暫存器值命令。
命令程式碼:70h
1.2 Nand Flash 控制器工作原理
對Nand Flash儲存晶片進行操作, 必須通過Nand Flash控制器的專用暫存器才能完成。所以,不能對Nand
Flash進行匯流排操作。而Nand Flash的寫操作也必須塊方式進行。對Nand Flash的讀操作可以按位元組讀取。
1.2.1 Nand Flash控制器特性
1. 支援對Nand Flash晶片的讀、檢驗、程式設計控制
2. 如果支援從Nand Flash啟動, 在每次重啟後自動將前Nand Flash的前4KB資料搬運到ARM的內部RAM中
3. 支援ECC校驗
1.2.2 Nand Flash控制器工作原理
Nand Flash控制器在其專用暫存器區(SFR)地址空間中對映有屬於自己的特殊功能暫存器,就是通過將Nand
Flash晶片的內設命令寫到其特殊功能暫存器中,從而實現對Nand flash晶片讀、檢驗和程式設計控制的。特殊功能
暫存器有:NFCONF、NFCMD、NFADDR、NFDATA、NFSTAT、NFECC。寄存詳細說明見下一節。
1.3 Nand flash 控制器中特殊功能暫存器詳細介紹
1. 配置暫存器(NFCONF)功能:用於對Nand Flash控制器的配置狀態進行控制。
在地址空間中地址:0x4E000000,其中:
Bit15:Nand Flash控制器使能位,置0代表禁止Nand Flash控制器,置1代表啟用Nand Flash控制器;
要想訪問Nand Flash晶片上儲存空間,必須啟用Nand Flash控制器。在復位後該位自動置0,因此在初始化時
必須將該位置為1。
Bit12:初始化ECC位,置1為初始化ECC;置0為不初始化ECC。
Bit11:Nand Flash晶片儲存空間使能位,置0代表可以對儲存空間進行操作;置1代表禁止對儲存空
間進行操作。在復位後,該位自動為1。
Bit10-8:TACLS位。根據此設定CLE&ALE的週期。TACLS的值範圍在0-7之間。
Bit6-4、2-0分別為:TWRPH0、TWRPH1位。設定寫操作的訪問週期。其值在0-7之間。
2. 命令暫存器(NFCMD)
功能:用於存放Nand flash晶片內設的操作命令。
在地址空間中地址:0x4E000004,其中:
Bit0-7:存放具體Nand flash晶片內設的命令值。其餘位保留以後用。
3. 地址暫存器(NFADDR)
功能:用於存放用於對Nand flash晶片儲存單元定址的地址值。
在地址空間中地址:0x4E000008,其中:
Bit0-7:用於存放地址值。因為本款Nand flash晶片只有I/O0-7的地址/資料複用引腳且地址是四周
期每次8位送入的,所以這裡只用到8位。其餘位保留待用。
4. 資料暫存器(NFDATA)
功能:Nand flash晶片所有內設命令執行後都會將其值放到該暫存器中。同時,讀出、寫入Nand flash
儲存空間的值也是放到該暫存器。
在地址空間中地址:0x4E00000C,其中:
Bit0-7:用於存放需要讀出和寫入的資料。其餘位保留代用。
5. 狀態暫存器(NFSTAT)
功能:用於檢測Nand flash晶片上次對其儲存空間的操作是否完成。
在地址空間中地址:0x4E000010,其中:
Bit0:置0表示Nand flash晶片正忙於上次對儲存空間的操作;置1表示Nand flash晶片準備好接收新
的對儲存空間操作的請求。
6. ECC校驗暫存器(NFECC)
功能:ECC校驗暫存器
在地址空間中地址:0x4E000014,其中:
Bit0Bit7: ECC0
Bit8Bit15: ECC1
Bit16Bit23: ECC2
1.4 Nand Flash 控制器中的硬體ECC介紹
1.4.1 ECC產生方法
ECC是用於對儲存器之間傳送資料正確進行校驗的一種演算法,分硬體ECC和軟體ECC演算法兩種,在
S3C2410的Nand Flash 控制器中實現了由硬體電路(ECC 生成器)實現的硬體ECC。1.4.2 ECC生成器工作過程
當寫入資料到Nand flash儲存空間時, ECC生成器會在寫入資料完畢後自動生成ECC碼,將其放入到
ECC0-ECC2。當讀出資料時Nand Flash 同樣會在讀資料完畢後,自動生成ECC碼將其放到ECC0-ECC2當
中。
1.4.3 ECC的運用
當寫入資料時,可以在每頁寫完資料後將產生的ECC碼放入到OOB指定的位置(Byte 6)去,這樣就完成了
ECC碼的儲存。這樣當讀出該頁資料時,將所需資料以及整個OOB讀出,然後將指定位置的ECC碼與讀出數
據後在ECC0-ECC1的實際產生的ECC碼進行對比,如果相等則讀出正確,若不相等則讀取錯誤需要進行重
讀。
2 在ADS下flash燒寫程式
2.1 ADS下flash燒寫程式原理及結構
基本原理:在windows環境下藉助ADS模擬器將在SDRAM中的一段儲存區域中的資料寫到Nand flash存
儲空間中。燒寫程式在縱向上分三層完成:
第一層: 主燒寫函式(完成將在SDRAM中的一段儲存區域中的資料寫到Nand flash儲存空間中);
第二層: 為第一層主燒寫函式提供支援的對Nand flash進行操作的頁讀、寫,塊擦除等函式;
第三層:為第二層提供具體Nand flash控制器中對特殊功能暫存器進行操作的核心函式,該層也是真正的
將資料能夠在SDRAM和Nand flash之間實現傳送的函式。
下面對其三層進行分述:
2.2 第三層實現說明
2.1.1 特殊功能暫存器定義
#define rNFCONF (*(volatile unsigned int *)0x4e000000)
#define rNFCMD (*(volatile unsigned char *)0x4e000004)
#define rNFADDR (*(volatile unsigned char *)0x4e000008)
#define rNFDATA (*(volatile unsigned char *)0x4e00000c)
#define rNFSTAT (*(volatile unsigned int *)0x4e000010)
#define rNFECC (*(volatile unsigned int *)0x4e000014)
#define rNFECC0 (*(volatile unsigned char *)0x4e000014)
#define rNFECC1 (*(volatile unsigned char *)0x4e000015)
#define rNFECC2 (*(volatile unsigned char *)0x4e000016)
2.1.2 操作的函式實現
1. 傳送命令
#define NF_CMD(cmd) {rNFCMD=cmd;}
2. 寫入地址
#define NF_ADDR(addr) {rNFADDR=addr;}
3. Nand Flash晶片選中
#define NF_nFCE_L() {rNFCONF&=~(1<<11);}
4. Nand Flash晶片不選中
#define NF_nFCE_H() {rNFCONF|=(1<<11);}
5. 初始化ECC
#define NF_RSTECC() {rNFCONF|=(1<<12);}
6. 讀資料#define NF_RDDATA() (rNFDATA)
7. 寫資料
#define NF_WRDATA(data) {rNFDATA=data;}
8. 獲取Nand Flash晶片狀態
#define NF_WAITRB() {while(!(rNFSTAT&(1<<0)));}
0/假: 表示Nand Flash晶片忙狀態
1/真:表示Nand Flash已經準備好
2.3 第二層實現說明
2.3.1 Nand Flash 初始化
void NF_Init(void)
{
/* 設定Nand Flash配置暫存器, 每一位的取值見1.3節 */
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
/* 復位外部Nand Flash晶片 */
NF_Reset();
}
2.3.2 Nand flash復位
static void NF_Reset(void)
{
int i;
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0xFF); /* 復位命令 */
for(i=0;i<10;i++); /* 等待tWB = 100ns. */
NF_WAITRB(); /* wait 200~500us; */
NF_nFCE_H(); /* 取消Nand Flash 選中*/
}
2.3.3 獲取Nand flash ID
返回值為Nand flash晶片的ID號
unsigned short NF_CheckId(void)
{
int i;
unsigned short id;
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x90); /* 傳送讀ID命令到Nand Flash晶片 */
NF_ADDR(0x0); /* 指定地址0x0,晶片手冊要求 */
for(i=0;i<10;i++); /* 等待tWB = 100ns. */
id=NF_RDDATA()<<8; /* 廠商ID(K9S1208V:0xec) */
id|=NF_RDDATA(); /* 裝置ID(K9S1208V:0x76) */
NF_nFCE_H(); /* 取消Nand Flash 選中*/
return id;
}2.3.4 Nand flash寫入
以頁為單位寫入.
引數說明:block 塊號
page 頁號
buffer 指向記憶體中待寫入Nand flash中的資料起始位置
返回值: 0:寫錯誤
1:寫成功
static int NF_WritePage(unsigned int block, unsigned int page, unsigned char *buffer)
{
int i;
unsigned int blockPage = (block<<5)+page;
unsigned char *bufPt = buffer;
NF_RSTECC(); /* 初始化 ECC */
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x0); /* 從A區開始寫 */
NF_CMD(0x80); /* 寫第一條命令 */
NF_ADDR(0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
for(i=0;i<512;i++)
{
NF_WRDATA(*bufPt++); /* 寫一個頁512位元組到Nand Flash晶片 */
}
/*
* OOB一共16 Bytes, 每一個位元組存放什麼由程式設計師自己定義, 通常,
* 我們在Byte0Byte2存ECC檢驗碼. Byte6 存放壞塊標誌.
*/
seBuf[0]=rNFECC0; /* 讀取ECC檢驗碼0 */
seBuf[1]=rNFECC1; /* 讀取ECC檢驗碼1 */
seBuf[2]=rNFECC2; /* 讀取ECC檢驗碼2 */
seBuf[5]=0xff; /* 非壞塊標誌 */
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]); /* 寫該頁的OOB資料塊 */
}
NF_CMD(0x10); /* 結束寫命令 */
/* 等待Nand Flash處於準備狀態 */
for(i=0;i<10;i++);
NF_WAITRB();
/* 傳送讀狀態命令給Nand Flash */
NF_CMD(0x70);
for(i=0;i<3;i++);
if (NF_RDDATA()&0x1)
{ /*如果寫有錯, 則標示為壞塊 */
NF_nFCE_H(); /* 取消Nand Flash 選中*/
NF_MarkBadBlock(block);
return 0;
} else { /* 正常退出 */
NF_nFCE_H(); /* 取消Nand Flash 選中*/
return 1;
}
}
2.3.5 Nand flash讀取
引數說明:block:塊號
page:頁號
buffer:指向將要讀取到記憶體中的起始位置
返回值:1:讀成功
0:讀失敗
static int NF_ReadPage(unsigned int block, unsigned int page, unsigned char *buffer)
{
int i;
unsigned int blockPage;
unsigned char ecc0, ecc1, ecc2;
unsigned char *bufPt=buffer;
unsigned char se[16];
page=page&0x1f;
blockPage=(block<<5)+page;
NF_RSTECC(); /* 初始化 ECC */
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x00); /* 從A區開始讀 */
NF_ADDR(0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 等待Nand Flash處於再準備狀態 */
for(i=0;i<10;i++);
NF_WAITRB(); /*等待 tR(max 12us) */
/* 讀整個頁, 512位元組 */
for(i=0;i<512;i++)
{
*bufPt++=NF_RDDATA();
}
/* 讀取ECC碼 */
ecc0=rNFECC0;
ecc1=rNFECC1;
ecc2=rNFECC2;
/* 讀取該頁的OOB塊 */
for(i=0;i<16;i++)
{
se[i]=NF_RDDATA();
}
NF_nFCE_H(); /* 取消Nand Flash 選中*/
/* 校驗ECC碼, 並返回 */
if(ecc0==se[0] && ecc1==se[1] && ecc2==se[2])
return 1;
else
return 0;
}
2.3.6 Nand flash標記壞塊
如果是壞塊, 通過寫OOB塊的Byte6把該塊標記為壞塊。
引數說明:block塊號
返回值:1:ok,成功完成標記。
0:表示寫OOB塊正確.
static int NF_MarkBadBlock(unsigned int block)
{
int i;
unsigned int blockPage=(block<<5);
seBuf[0]=0xff;
seBuf[1]=0xff;
seBuf[2]=0xff;
seBuf[5]=0x44; /* 設定壞塊標記 */
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x50); /* 從C區開始寫 */
NF_CMD(0x80); /* 傳送程式設計命令, 讓Nand Flash處理寫狀態 */
NF_ADDR(0x0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 寫OOB資料塊 */
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]);
}
NF_CMD(0x10); /* 結束寫命令 */
/* 等待NandFlash準備好 */
for(i=0;i<10;i++); /* tWB = 100ns. */
NF_WAITRB(); /*讀NandFlash的寫狀態 */
NF_CMD(0x70);
for(i=0;i<3;i++); /* twhr=60ns */
if (NF_RDDATA()&0x1)
{
NF_nFCE_H(); /* 取消Nand Flash 選中*/
return 0;
} else {
NF_nFCE_H(); /* 取消Nand Flash 選中*/
}
return 1;
}
2.3.7 Nand Flash檢查壞塊
檢查指定塊是否是壞塊.
引數說明:block:塊號
返回值:1:指定塊是壞塊
0:指定塊不是壞塊。
static int NF_IsBadBlock(U32 block)
{
int i;
unsigned int blockPage;
U8 data;
blockPage=(block<<5);
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x50); /* Read OOB資料塊 */
NF_ADDR(517&0xf); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 等待NandFlash準備好 */
for(i=0;i<10;i++); /* wait tWB(100ns) */
NF_WAITRB();
/* 讀取讀出值 */
data=NF_RDDATA();
NF_nFCE_H(); /* 取消Nand Flash 選中*/
/* 如果data不為0xff時, 表示該塊是壞塊 */
if(data != 0xff)
return 1;
else
return 0;
}
2.3.8 擦除指定塊中資料
引數說明:block 塊號
返回值:0:擦除錯誤。(若是壞塊直接返回0;若擦除出現錯誤則標記為壞塊然後返回0) 1:成功擦除。
static int NF_EraseBlock(unsigned int block)
{
unsigned int blockPage=(block<<5);
int i;
/* 如果該塊是壞塊, 則返回 */
if(NF_IsBadBlock(block))
return 0;
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x60); /* 設定擦寫模式 */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) , 是基於塊擦*/
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
NF_CMD(0xd0); /* 傳送擦寫命令, 開始擦寫 */
/* 等待NandFlash準備好 */
for(i=0;i<10;i++); /* tWB(100ns) */
NF_WAITRB();
/* 讀取操作狀態 */
NF_CMD(0x70);
if (NF_RDDATA()&0x1)
{
NF_nFCE_H(); /* 取消Nand Flash 選中*/
NF_MarkBadBlock(block); /* 標記為壞塊 */
return 0;
} else {
NF_nFCE_H(); /* 取消Nand Flash 選中*/
return 1;
}
}
2.4 第一層的實現
2.4.1 NandFlash燒寫主函式說明
引數說明: block 塊號
srcAddress SDRAM中資料起始地址
fileSize 要燒寫的資料長度
返回值: 無
void K9S1208_Program(unsigned int block, unsigned int srcAddress, unsigned int fileSize)
{
int i;
int programError=0;
U32 blockIndex;
U8 *srcPt, *saveSrcPt;
srcPt=(U8 *)srcAddress; /* 檔案起始地址 */
blockIndex = block; /* 塊號 */ while(1)
{
saveSrcPt=srcPt;
/* 如果當前塊是壞塊, 跳過當前塊 */
if(NF_IsBadBlock(blockIndex))
{
blockIndex++; /* 到下一個塊 */
continue;
}
/* 在寫之前, 必須先擦除, 如果擦除不成功, 跳過當前塊 */
if(!NF_EraseBlock(blockIndex))
{
blockIndex++; /* 到下一個塊 */
continue;
}
/* 寫一個塊, 一塊有32頁 */
for(i=0;i<32;i++)
{
/* 寫入一個頁, 如果出錯, 停止寫當前塊 */
if(!NF_WritePage(blockIndex,i,srcPt))
{
programError=1;
break;
}
/* 如果操作正常, 檔案的寫位置加上1頁偏移,到下一頁的起始位置 */
srcPt+=512;
/* 如果寫地址沒有超過檔案長度, 繼續; 超出則終止寫 */
if((U32)srcPt>=(srcAddress+fileSize))
break;
}
/* 如果寫一個塊時, 其中某一頁寫失敗, 則把寫地址恢復寫該塊之前, 並跳過當前塊 */
if(programError==1)
{
blockIndex++;
srcPt=saveSrcPt;
programError=0;
continue;
}
/* 如果寫地址沒有超過檔案長度, 繼續; 超出則終止寫 */
if((U32)srcPt >= (srcAddress+fileSize))
break;
/* 如果正常寫成功, 繼續寫下一個塊 */
blockIndex++;
}
}3 在U-BOOT對Nand Flash的支援
3.1 U-BOOT對從Nand Flash啟動的支援
3.1.1 從Nand Flash啟動U-BOOT的基本原理
1. 前4K的問題
如果S3C2410被配置成從Nand Flash啟動(配置由硬體工程師在電路板設定), S3C2410的Nand Flash控制器
有一個特殊的功能, 在S3C2410上電後, Nand Flash控制器會自動的把Nand Flash上的前4K資料搬移到4K內部
RAM中, 並把0x00000000設定內部RAM的起始地址, CPU從內部RAM的0x00000000位置開始啟動。這個過
程不需要程式干涉。
程式設計師需要完成的工作,是把最核心的啟動程式放在Nand Flash的前4K中。
2. 啟動程式的安排
由於Nand Flash控制器從Nand Flash中搬移到內部RAM的程式碼是有限的,所以, 在啟動程式碼的前4K裡,我
們必須完成S3C2410的核心配置以及把啟動程式碼(UBOOT)剩餘部分搬到RAM中執行。以UBOOT為例, 前4K
完成的主要工作, 見第四部分的2.2節。
3.1.2 支援Nand Flash啟動程式碼說明
首先在include/configs/crane2410.h中加入CONFIG_S3C2410_NAND_BOOT, 如下:
#define CONFIG_S3C2410_NAND_BOOT 1
支援從Nand Flash中啟動.
1. 執行Nand Flash初始化
下面程式碼在cpu/arm920t/start.S中
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START @安裝棧的起始地址
mov fp, #0 @初始化幀指標暫存器
bl nand_reset @跳到復位C函式去執行
...
DW_STACK_START:
.word STACK_BASE+STACK_SIZE4
2. nand_reset C程式碼
下面程式碼被加在/board/crane2410/crane2410.c中
void nand_reset(void)
{
int i;
/* 設定Nand Flash控制器 */
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
/* 給Nand Flash晶片傳送復位命令 */
NF_nFCE_L();
NF_CMD(0xFF);
for(i=0; i<10; i++);
NF_WAITRB(); NF_nFCE_H();
}
3. 從Nand Flash中把UBOOT拷貝到RAM
@read UBOOT from Nand Flash to RAM
ldr r0, =UBOOT_RAM_BASE @ 設定第1個引數: UBOOT在RAM中的起始地址
mov r1, #0x0 @ 設定第2個引數:Nand Flash的起始地址
mov r2, #0x20000 @ 設定第3個引數: UBOOT的長度(128KB)
bl nand_read_whole @ 呼叫nand_read_whole(), 該函式在board/crane2410/crane2410.c中
tst r0, #0x0 @ 如果函式的返回值為0,表示執行成功.
beq ok_nand_read @ 執行記憶體比較
4. 從Nand Flash中把資料讀入到RAM中
int nand_read_whole(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
/* 如果起始地址和長度不是512位元組(1頁)的倍數, 則返回錯誤程式碼 */
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
return 1;
}
/* 啟用Nand Flash */
NF_nFCE_L();
for(i=0; i<10; i++);
i = start_addr;
while(i < start_addr + size) {
/* 讀A區 */
rNFCMD = 0;
/* 寫入讀取地址 */
rNFADDR = i & 0xff;
rNFADDR = (i >> 9) & 0xff;
rNFADDR = (i >> 17) & 0xff;
rNFADDR = (i >> 25) & 0xff;
NF_WAITRB();
/* 讀出一頁(512位元組) */
for(j=0; j < NAND_SECTOR_SIZE; j++, i++) {
*buf = (rNFDATA & 0xff);
buf++;
}
}
/* 停止驅動Nand Flash */
NF_nFCE_H();
return 0;
}5. 校查搬移後的資料
把RAM中的前4K與內部中前4K進行比較, 如果完全相同, 則表示搬移成功.
ok_nand_read:
mov r0, #0x00000000 @內部RAM的起始地址
ldr r1, =UBOOT_RAM_BASE @UBOOT在RAM中的起始地址
mov r2, #0x400 @比較1024次, 每次4位元組, 4 bytes * 1024 = 4Kbytes
go_next: @ 比較1024次, 每次4個位元組
ldr r3, [r0], #4
ldr r4, [r1], #4
teq r3, r4
bne notmatch
subs r2, r2, #4
beq done_nand_read
bne go_next
notmatch:
1:b 1b
done_nand_read:
mov pc, r10
3.2 U-BOOT對Nand Flash命令的支援
在UBOOT下對Nand Flash的支援主要是在命令列下實現對nand flash的操作。對nand flash實現的命令
為:nand info、nand device、nand read、nand write、nand erease、nand bad。
用到的主要資料結構有:struct nand_flash_dev、struct nand_chip。前者包括主要的晶片型號、儲存容量、
裝置ID、I/O匯流排寬度等資訊;後者是具體對nand flash進行操作時用到的資訊。
3.2.1 主要資料結構介紹
1. struct nand_flash_dev資料結構
該資料結構在include/linux/mtd/nand.h中定義,在include/linux/mtd/nand_ids.h中賦初值。
struct nand_flash_dev {
char *name; /* 晶片名稱 */
int manufacture_id; /* 廠商ID */
int model_id; /* 模式ID */
int chipshift; /* Nand Flash地址位數 */
char page256; /* 表明是否時256位元組一頁。1:是;0:否。*/
char pageadrlen; /* 完成一次地址傳送需要往NFADDR中傳送幾次。*/
unsigned long erasesize; /* 一次塊擦除可以擦除多少位元組 */
int bus16; /* 地址線是否是16位,1:是;0:否 */
};
2. struct nand_chip資料結構
該資料結構在include/linux/mtd/nand.h中定義. 該結構體定義出一個Nand Flash裝置陣列:
struct nand_chip nand_dev_desc[CFG_MAX_NAND_DEVICE];
該陣列在nand_probe()中對其進行初始化.
struct nand_chip {
int page_shift; /* Page 地址位數 */
u_char *data_buf; /* 本次讀出的一頁資料 */
u_char *data_cache; /* 讀出的一頁資料 */
int cache_page; /* 上次操作的頁號 */u_char ecc_code_buf[6]; /* ECC校驗碼 */
u_char reserved[2];
char ChipID; /* 晶片ID號 */
struct Nand *chips; /* Nand Flash晶片列表, 表示支援幾個晶片為一個裝置*/
int chipshift;
char* chips_name; /* Nand Flash晶片名稱 */
unsigned long erasesize; /* 塊擦寫的大小 */
unsigned long mfr; /* 廠商ID */
unsigned long id; /* 模式ID */
char* name; /* 裝置名稱 */
int numchips; /* 有幾塊Nand Flash晶片 */
char page256; /* 一頁是256位元組, 還是512位元組 */
char pageadrlen; /* 頁地址的長度 */
unsigned long IO_ADDR; /* 用於對nand flash進行定址的地址值存放處 */
unsigned long totlen; /* Nand Flash總共大小 */
uint oobblock; /* 一頁的大小。本款nand flash為512 */
uint oobsize; /* spare array大小。本款nand flash為16 */
uint eccsize; /* ECC 大小 */
int bus16; /* 地址線是否是16位,1:是;0:否 */
};
3.2.2 支援的命令函式說明
1. nand info/nand device
功能:顯示當前nand flash晶片資訊。
函式呼叫關係如下(按先後順序):
static void nand_print(struct nand_chip *nand) ;
2. nand erase
功能:擦除指定塊上的資料。
函式呼叫關係如下(按先後順序):
int nand_erase(struct nand_chip* nand, size_t ofs, size_t len, int clean);
3. nand bad
功能:顯示壞塊。
函式呼叫關係如下(按先後順序):
static void nand_print_bad(struct nand_chip* nand);
int check_block (struct nand_chip *nand, unsigned long pos);
4. nand read
功能:讀取nand flash資訊到SDRAM。
函式呼叫關係如下(按先後順序):
int nand_rw (struct nand_chip* nand, int cmd,size_t start, size_t len, size_t * retlen, u_char * buf);
static int nand_read_ecc(struct nand_chip *nand, size_t start, size_t len,
size_t * retlen, u_char *buf, u_char *ecc_code);
static void NanD_ReadBuf (struct nand_chip *nand, u_char * data_buf, int cntr);
READ_NAND(adr);
5. nand write
功能:從SDRAM寫資料到nand flash中。
函式呼叫關係如下(按先後順序):int nand_rw (struct nand_chip* nand, int cmd,size_t start, size_t len, size_t * retlen, u_char * buf);
static int nand_write_ecc (struct nand_chip* nand, size_t to, size_t len,
size_t * retlen, const u_char * buf, u_char * ecc_code);
static int nand_write_page (struct nand_chip *nand, int page, int col, int last, u_char * ecc_code);
WRITE_NAND(d , adr);
3.2.3 U-BOOT支援Nand Flash命令移植說明
1. 設定配置選項
在CONFIG_COMMANDS中, 開啟CFG_CMD_NAND選項.
#define CONFIG_COMMANDS \
(CONFIG_CMD_DFL | \
CFG_CMD_CACHE | \
CFG_CMD_NAND | \
/*CFG_CMD_EEPROM |*/ \
/*CFG_CMD_I2C |*/ \
/*CFG_CMD_USB |*/ \
CFG_CMD_PING | \
CFG_CMD_REGINFO | \
CFG_CMD_DATE | \
CFG_CMD_ELF)
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
#define CFG_NAND_BASE 0x4E000000 /* Nand Flash控制器在SFR區中起始暫存器地址 */
#define CFG_MAX_NAND_DEVICE 1 /* 支援的最在Nand Flash資料 */
#define SECTORSIZE 512 /* 1頁的大小 */
#define NAND_SECTOR_SIZE SECTORSIZE
#define NAND_BLOCK_MASK (NAND_SECTOR_SIZE – 1) /* 頁掩碼 */
#define ADDR_COLUMN 1 /* 一個位元組的Column地址 */
#define ADDR_PAGE 3 /* 3位元組的頁塊地址, A9A25*/
#define ADDR_COLUMN_PAGE 4 /* 總共4位元組的頁塊地址 */
#define NAND_ChipID_UNKNOWN 0x00 /* 未知晶片的ID號 */
#define NAND_MAX_FLOORS 1
#define NAND_MAX_CHIPS 1
/* Nand Flash命令層底層介面函式 */
#define WRITE_NAND_COMMAND(d, adr) do {rNFCMD = d;} while(0)
#define WRITE_NAND_ADDRESS(d, adr) do {rNFADDR = d;} while(0)
#define WRITE_NAND(d, adr) do {rNFDATA = d;} while(0)
#define READ_NAND(adr) (rNFDATA)
#define NAND_WAIT_READY(nand) {while(!(rNFSTAT&(1<<0)));}
#define NAND_DISABLE_CE(nand) {rNFCONF |= (1<<11);}
#define NAND_ENABLE_CE(nand) {rNFCONF &= ~(1<<11);}
/* 下面一組操作對Nand Flash無效 */
#define NAND_CTL_CLRALE(nandptr)
#define NAND_CTL_SETALE(nandptr)
#define NAND_CTL_CLRCLE(nandptr)
#define NAND_CTL_SETCLE(nandptr)/* 允許Nand Flash寫校驗 */
#define CONFIG_MTD_NAND_VERIFY_WRITE 1
#endif /* CONFIG_COMMANDS & CFG_CMD_NAND*/
2. 加入自己的Nand Flash晶片型號
在include/linux/mtd/ nand_ids.h中的對如下結構體賦值進行修改:
static struct nand_flash_dev nand_flash_ids[] = {
......
{"Samsung K9F1208U0B", NAND_MFR_SAMSUNG, 0x76, 26, 0, 4, 0x4000, 0},
.......
}
這樣對於該款Nand Flash晶片的操作才能正確執行。
3. 編寫自己的Nand Flash初始化函式
在board/crane2410/crane2410.c中加入nand_init()函式.
void nand_init(void)
{
/* 初始化Nand Flash控制器, 以及Nand Flash 晶片 */
nand_reset();
/* 呼叫nand_probe()來檢測晶片型別 */
printf ("%4lu MB\n", nand_probe(CFG_NAND_BASE) >> 20);
}
該函式在啟動時被start_armboot()呼叫.
4 在Linux對Nand Flash的支援
4.1 Linux下Nand Flash呼叫關係
4.1.1 Nand Flash裝置新增時資料結構包含關係
struct mtd_partition partition_info[]
--> struct s3c2410_nand_set nandset
--> struct s3c2410_plat form_nand superlpplatfrom
--> struct platform_device s3c_device_nand
在該資料結構的name欄位的初始化值"s3c2410-nand",必須與Nand Flash裝置驅動註冊時
struct device_driver結構中的name欄位相同,因為platfrom bus是依靠名字來匹配的.
--> struct platform_device *smdk2410_devices[]
4.1.2 Nand Flash設備註冊時資料結構包含關係
struct device_driver s3c2410_n and_driver
-->struct device *dev
該資料構由系統分配.
-->struct platform_device *pdev
-->struct s3c2410_plat form_nand *plat
-->struct s3c2410_nand_set nset
-->struct mtd_partition
4.1.3 當發生系統呼叫時資料結構呼叫關係
struct mtd_info它的*priv指向chip
-->struct nand_chip
它的*priv指向nmtd
-->struct s3c2410_nand_mtd
它是s3c2410_nand_info的一個欄位
-->s3c2410_nand_info
它被設為Nand Flash裝置驅動的私有資料結構,在Nand Flash裝置驅動註冊時分配空間.
-->struct device
4.2 Linux下Nand Flash驅動主要資料結構說明
4.2.1 s3c2410專有資料結構
1. s3c2410_nand_set
struct s3c2410_nand_set {
int n r_chips; /* 晶片的數目 */
int n r_partitions; /* 分割槽的數目 */
char *n ame; /* 集合名稱 */
int nr_map; /* 可選, 底層邏輯到物理的晶片數目 */
struct mtd_partition partitions; /* 分割槽列表 */
};
2. s3c2410_platform_and
struct s3c2410_platform_nand {
/* timing information for controller, all times in nanoseconds */
int tacls; /* 從CLE/ALE有效到 nWE/nOE的時間 */
int twrph0; /* nWE/nOE的有效時間 */
int twrph1; /* 從釋放CLE/ALE到nWE/nOE不活動的時間 */
int nr_sets; /* 集合數目 */
struct s3c2410_nand_set sets; /* 集合列表 */
/* 根據晶片編號選擇有效集合 */
void (*select_chip)(struct s3c2410_nand_set , int chip);
};
3. s3c2410_nand_mtd
在drivers/mtd/nand/s3c2410.c中,
struct s3c2410_nand_mtd {
struct mtd_info mt d; /* MTD 資訊 */
struct nand_chip ch ip; /* nand flash 晶片資訊 */
struct s3c2410_nand_set set; /* nand flash 集合 */
struct s3c2410_nand_info *info; /* nand flash 資訊 */
int scan _res;
};
4. s3c2410_nand_info
struct s3c2410_nand_info {
/* mtd info */
struct nand_hw_control co ntroller; /* 硬體控制器 */
struct s3c2410_nand_mtd *mt ds; /* MTD 裝置表 */
struct s3c2410_platform_nand platform; /* Nand 裝置的平臺 */ /* device info */
struct device *device; /* 裝置指標 */
struct resource *area; /* 資源指標 */
struct clk *clk; /* N and Flash 時鐘 */
void __iomem *reg s; /* 暫存器基地址(map後的邏輯地址) */
int mt d_count; /* MTD的數目 */
unsigned char is_s3c2440;
};
5. struct clk
在arch/arm/machs3c2410/clock.h中
struct clk {
struct list_head list ; /* clock 列表結點 */
struct module *o wner; /* 所屬模組 */
struct clk *paren t; /* 父結點 */
const char *n ame; /* 名稱 */
int id; /* 編號 */
atomic_t used; /* 使用者計數 */
unsigned long rate; /* 時鐘速率 */
unsigned long ctrlbit; /* 控制位 */
int (*en able)(struct clk *, int enable); /* Clock開啟方法 */
};
4.2.2 Linux 通用資料結構說明
1. device_driver
include/linux/device.h
struct device_driver {
const char * n ame; /* 驅動名稱 */
struct bus_type * b us; /* 匯流排型別 */
struct completion unloaded; /* 解除安裝事件通知機制 */
struct kobject kobj; /* sys中的物件 */
struct klist klist _devices; /* 裝置列表 */
struct klist_node knode_bus; /* 匯流排結點列表 */
struct module * o wner;/* 所有者 */
/* 裝置驅動通用方法 */
int (*probe) (struct device * dev) ; /* 探測裝置 */
int (*remove) (struct device * dev) ; /* 移除裝置 */
void (*shutdown) (struct device * dev) ; /* 關閉裝置 */
/* 掛起裝置 */
int (*suspend) (struct device * dev, pm_messag e_t state, u32 level) ;
int (*resume) (struct device * dev, u32 level) ; /* 恢復 */
};
2. platform_device
include/linux/device.h
struct platform_device {
const char * name; /* 名稱 */
u32 id; /* 裝置編號, -1表示不支援同類多個裝置 */
struct device dev; /* 裝置 */
u32 n um_resources; /* 資源數 */
struct resource * resource; /* 資源列表 */};
3. resource
struct resource {
const char name; /* 資源名稱 */
unsigned long start, end; /* 開始位置和結束位置 */
unsigned long flags; /* 資源型別 */
/* 資源在資源樹中的父親,兄弟和孩子 */
struct resource *parent, *sibling, *child;
};
4. device
include/linux/device.h
struct device {
struct klist klist _children; /* 在裝置列表中的孩子列表 */
struct klist_node knode_parent; /* 兄弟結點 */
struct klist_node knode_driver; /* 驅動結點 */
struct klist_node knode_bus; /* 匯流排結點 */
struct device parent; /* 父親 */
struct kobject kobj; /* sys結點 */
char bus_id[BUS_ID_SIZE];
struct semaphore sem; /* 同步驅動的訊號量 */
struct bus_type * bus; /* 匯流排型別 */
struct device_driver *driver; /* 裝置驅動 */
void *driver_dat a; /* 驅動的私有資料 */
void *plat form_data; /* 平臺指定的資料,為device核心驅動保留 */
void *firmw are_data; /* 韌體指定的資料,為device核心驅動保留 */
struct dev_pm_info power; /* 裝置電源管理資訊 */
u64 *dma_mask; /* DMA掩碼 */
u64 co herent_dma_mask;
struct list_head dma_pools; /* DMA緩衝池 */
struct dma_coherent_mem *dma_mem; /* 連續DMA記憶體的起始位置 */
void (*release) (struct device * dev) ; /* 釋放設定方法 */
};
5. nand_hw_control
include/linux/mtd/nand.h
struct nand_hw_control {
spinlock_t lock; /* 自旋鎖,用於硬體控制 */
struct nand_chip *active; /* 正在處理MTD裝置 */
wait_queue_head_t wq; /* 等待佇列 */
};
6. nand_chip
include/linux/mtd/nand.h
struct nand_chip {
void __iomem *IO_ADDR_R; /* 讀地址 */
void __iomem *IO_ADDR_W; /* 寫地址 */ /* 位元組操作 */
u_char (*read_b yte)(struct mtd_info *mtd); /* 讀一個位元組 */
void (*w rite_byte)(struct mtd_info *mtd, u_char byte); /* 寫一個位元組 */
/* 雙位元組操作 */
u16 (*read_w ord)(struct mtd_info mtd); /* 讀一個字 */
void (*w rite_word)(struct mtd_info *mtd, u16 word); /* 寫一個字 */
/* buffer操作 */
void (*w rite_buf)(struct mtd_info *mtd, const u_char *buf, int len);
void (*read_b uf)(struct mtd_info *mtd, u_char *buf, int len);
int (*verify_b uf)(struct mtd_info *mtd, const u_char *buf, int len);
/* 選擇一個操作晶片 */
void (*select _chip)(struct mtd_info *mtd, int chip);
/* 壞塊檢查操作 */
int (*b lock_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
/* 壞塊標記操作 */
int (*b lock_markbad)(struct mtd_info *mtd, loff_t ofs);
/* 硬體控制操作 */
void (*h wcontrol)(struct mtd_info *mtd, int cmd);
/* 裝置準備操作 */
int (*dev_ready) (struct mtd_info *mtd);
/* 命令傳送操作 */
void (*cmdfun c)(struct mtd_info *mtd, unsigned command, int column, int
page_addr);
/* 等待命令完成 */
int (*w aitfunc)(struct mtd_info *mtd, struct nand_chip *this, int state);
/* 計算ECC碼操作 */
int (*calculat e_ecc)(struct mtd_info *mtd, const u_char *dat, u_char
*ecc_code);
/* 資料糾錯操作 */
int (*co rrect_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc,
u_char *calc_ecc) ;
/* 開啟硬體ECC */
void (*en able_hwecc)(struct mtd_info *mtd, int mode);
/* 擦除操作 */
void (*erase_cmd) (struct mtd_info *mtd, int page);
/* 檢查壞塊表 */
int (*scan _bbt)(struct mtd_info *mtd);
int eccmo de; /* ECC模式 */
int eccsize; /* E CC 計算時使用的位元組數 */
int eccb ytes; /* ECC 碼的位元組數 */
int eccst eps; /* ECC 碼計算的步驟數 */
int ch ip_delay; /* 晶片的延遲時間 */
spinlock_t chip_lock; /* 晶片訪問的自旋鎖 */
wait_queue_head_t wq; /* 晶片訪問的等待佇列 */
nand_state_t state; /* Nand Flash狀態 */
int pag e_shift; /* 頁右移的位數,即column地址位數 */
int ph ys_erase_shift; /* 塊右移的位數, 即column和頁一共的地址位數 */
int b bt_erase_shift; /* 壞塊頁表的位數 */
int ch ip_shift; /* 該晶片總共的地址位數 */
u_char *dat a_buf; /* 資料緩衝區 */
u_char *o ob_buf; /* oob緩衝區 */
int o obdirty; /* oob緩衝區是否需要重新初始化 */
u_char *dat a_poi; /* 資料緩衝區指標 */
unsigned int options; /* 晶片專有選項 */
int b adblockpos;/* 壞塊標示位元組在OOB中的位置 */
int n umchips; /* 晶片的個數 */ unsigned long chipsize; /* 在多個晶片組中, 一個晶片的大小 */
int pag emask; /* 每個晶片頁數的遮蔽字, 通過它取出每個晶片包含多少個頁 */
int pag ebuf; /* 在頁緩衝區中的頁號 */
struct nand_oobinfo *autooob; /* oob資訊 */
uint8_t *bbt; /* 壞塊頁表 */
struct nand_bbt_descr *bbt_td; /* 壞塊表描述 */
struct nand_bbt_descr *bbt_md; /* 壞塊表映象描述 */
struct nand_bbt_descr *badblock_pattern; /* 壞塊檢測模板 */
struct nand_hw_control *controller; /* 硬體控制 */
void *priv; /* 私有資料結構 */
/* 進行附加錯誤檢查 */
int (*errst at)(struct mtd_info *mtd, struct nand_chip *this, int state, int
status, int page);
};
7. mtd_info
include/linux/mtd/mtd.h
struct mtd_info {
u_char type; /* 裝置型別 */
u_int32_t flags; /* 裝置標誌位組 */
u_int32_t size; /* 總共裝置的大小 */
u_int32_t erasesize; /* 擦除塊的大小 */
u_int32_t oobblock; /* OOB塊的大小,如:512個位元組有一個OOB */
u_int32_t oobsize; /* OOB資料的大小,如:一個OOB塊有16個位元組 */
u_int32_t ecctype; /* ECC校驗的型別 */
u_int32_t eccsize; /* E CC碼的大小 */
char *name; /* 裝置名稱 */
int index; /* 裝置編號 */
/* oobinfo資訊,它可以通過 MEMSETOOBINFO ioctl命令來設定 */
struct nand_oobinfo oobinfo;
u_int32_t oobavail; /* OOB區的有效位元組數,為檔案系統提供 */
/* 資料擦除邊界資訊 */
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
u_int32_t bank_size; /* 保留 */
/* 擦除操作 */
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
/* 指向某個執行程式碼位置 */
int (*point) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char **mtdbuf);
/* 取消指向 */
void (*unpoint) (struct mtd_info *mtd, u_char * addr, lo ff_t from, size_t len);
/* 讀/寫操作 */
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
/* 帶ECC碼的讀/寫操作 */
int (*read_ecc) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
int (*write_ecc) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel); /* 帶OOB碼的讀/寫操作 */
int (*read_oob) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
u_char *buf);
int (*write_oob) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf);
/* 提供訪問保護暫存器區的方法 */
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_user_pro t_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
/* 提供readv和writev方法 */
int (*readv) (struct mtd_info *mtd, struct kvec *vecs, un signed long count,
loff_t from, size_t *retlen);
int (*readv_ecc) (struct mtd_info *mtd, struct kvec *vecs, un signed long count,
loff_t from, size_t *retlen, u_char *eccbuf,
struct nand_oobinfo *oobsel);
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen);
int (*writev_ecc) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen,
u_char *eccbuf, struct nand_oobinfo *oobsel);
/* 同步操作 */
void (*sync) (struct mtd_info *mtd);
/* 晶片級支援的加/解鎖操作 */
int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
/* 電源管理操作 */
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
/* 壞塊管理操作 */
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
/* 重啟前的通知事件 */
struct notifier_block reboot_notifier;
void *priv; /* 私有資料結構 */
struct module *owner; /* 模組所有者 */
int usecount; /* 使用次數 */
};
4.3 Linux下Nand Flash驅動說明4.3.1 註冊driver_register
通過module_init(s3c2410_nand_init);註冊Nand Flash驅動. 在 s3c2410_nand_init ()中通過driver_register()註冊
s3c2410_nand_driver驅動程式,如下所示:
static struct device_driver s3c2410_n and_driver = {
.name = "s3c2410-n and",
.bus = &plat form_bus_type, /* 在drivers/base/platform.c中定義 */
.probe = s3c2410_n and_probe,
.remove = s3c2410_n and_remove,
};
4.3.2 探測裝置probe
在註冊的Nand Flash驅動程式中, probe方法為s3c2410_nand_probe(). s3c2410_nand_probe()再呼叫
s3c24xx_nand_probe(). 在該函式中, 把*info作為Nand Flash驅動的私有資料結構, 並通過dev_set_drvdata(dev,
info)把*info儲存在*device的*driver_data欄位中.然後通過clk_get(dev, "nand")獲取Nand Flash的時鐘資
源, clk_use(in fo->clk)增加時鐘資源的使用計數, clk_enable(info->clk)開啟資源.填寫*info的其它欄位,
其中包括:
1. 通過request_mem_region()為Nand Flash暫存器區申請I/O記憶體地址空間區,並通過ioremap()把它對映到虛
擬地址空間.
2. 呼叫s3c2410_nand_inithw()初始化Nand Flash控制器.
3. 為mtd裝置分配裝置資訊的儲存空間.
4. 對當前mtd裝置,呼叫s3c2410_nand_init_chip()進行初始化.
5. 對當前mtd裝置, 呼叫nand_scan()檢測Nand Flash晶片, nand_scan()函式在drivers/mtd/nand/nand_base.c中
定義.該函式的作用是初始化struct nand_chip中一些方法, 並從Nand Flash中讀取晶片ID, 並初始化struct
mtd_info中的方法.
6. 對當前mtd裝置,加入其分割槽資訊.
7. 如果還有更多mtd裝置,到4執行.
4.3.3 初始化Nand Flash控制器
s3c2410_nand_inithw()函式會初始化Nand Flash控制器, 通過設定Nand Flash控制暫存器(S3C2410_NFCONF)來
完成, 這裡最重要的是根據S3C2410的PCLK計算出tacls, twrph0以及twrph1值.
4.3.4 移除裝置
s3c2410_nand_remove()當裝置被移除時,被device核心驅動呼叫.它完成的主要工作如下:
1. 把*device的*driver_data欄位置空.
2. 釋放mtd裝置資訊.
3. 釋放clk資源.
4. 通過iounmap()取消映地址空間.
5. 釋放申請的I/O記憶體資源.
6. 釋放裝置私有資料*info的空間.
4.3.5 Nand Flash晶片初始化
s3c2410_nand_init_chip()初始化struct nand_chip中的一些主要欄位以及方法.其中主要包括的方法有:
1. s3c2410_nand_hwcontrol(); 硬體控制
2. s3c2410_nand_devready(); 裝置是否準備好
3. s3c2410_nand_write_buf(); 寫一個buffer到nand flash
4. s3c2410_nand_read_buf(); 讀一個buffer到nand flash
5. s3c2410_nand_select_chip(); 選擇操作晶片
如果支援ECC硬體校驗,還設定如下方法: 1. s3c2410_nand_correct_data(); 通過ECC碼校正資料
2. s3c2410_nand_enable_hwecc(); 開啟硬體ECC檢查
3. s3c2410_nand_calculate_ecc(); 計算ECC碼
4.3.6 讀Nand Flash
當對nand flash的裝置檔案(nand flash在/dev下對應的檔案)執行系統呼叫read(),或在某個檔案系統中對該
裝置進行讀操作時. 會呼叫struct mtd_info中的read方法,他們預設呼叫函式為nand_read(),在
drivers/mtd/nand/nand_base.c中定義.nand_read()呼叫nand_do_read_ecc(),執行讀操作. 在
nand_do_read_ecc()函式中,主要完成如下幾項工作:
1. 會呼叫在nand flash驅動中對struct nand_chip過載的select_chip方法,即
s3c2410_nand_select_chip()選擇要操作的MTD晶片.
2. 會呼叫在struct nand_chip中系統預設的方法cmdfunc傳送讀命令到nand flash.
3. 會呼叫在nand flash驅動中對struct nand_chip過載的read_buf(),即s3c2410_nand_read_buf()
從Nand Flash的控制器的資料暫存器中讀出資料.
4.
S3C2410板的Nand Flash支援由兩部分組成:Nand Flash控制器(整合在S3C2410 CPU)和Nand Flash儲存
晶片(K9F1208U0B)兩大部分組成。當要訪問Nand Flash中的資料時,必須通過Nand Flash控制器傳送命
令才能完成。所以, Nand Flash相當於S3C2410的一個外設,而不位於它的記憶體地址區.
1.1 Nand flash晶片工作原理
Nand flash晶片型號為Samsung K9F1208U0B,資料儲存容量為64MB,採用塊頁式儲存管理。8個I/O
引腳充當資料、地址、命令的複用埠。
1.1.1 晶片內部儲存佈局及儲存操作特點
一片Nand flash為一個裝置(device), 其資料儲存分層為:
1裝置(Device) = 4096 塊(Blocks)
1塊(Block) = 32頁/行(Pages/rows) ;頁與行是相同的意思,叫法不一樣
1塊(Page) = 528位元組(Bytes) = 資料塊大小(512Bytes) + OOB塊大小(16Bytes)
在每一頁中,最後16個位元組(又稱OOB)用於Nand Flash命令執行完後設置狀態用,剩餘512個位元組又
分為前半部分和後半部分。可以通過Nand Flash命令00h/01h/50h分別對前半部、後半部、OOB進行定位通過
Nand Flash內建的指標指向各自的首地址。
儲存操作特點:
1. 擦除操作的最小單位是塊。
2. Nand Flash晶片每一位(bit)只能從1變為0,而不能從0變為1,所以在對其進行寫入操作之前要一定將相應
塊擦除(擦除即是將相應塊得位全部變為1).
3. OOB部分的第六位元組(即517位元組)標誌是否是壞塊,如果不是壞塊該值為FF,否則為壞塊。
4. 除OOB第六位元組外,通常至少把OOB的前3個位元組存放Nand Flash硬體ECC碼(關於硬體ECC碼請參看
Nandflash 控制器一節).
1.1.2 重要晶片引腳功能
I/O0I/O7:複用引腳。可以通過它向nand flash晶片輸入資料、地址、nand flash命令以及輸出資料和操作
狀態資訊。
CLE(Command Latch Enable): 命令鎖存允許
ALE(Address Lactch Enable): 地址鎖存允許
CE: 晶片選擇
RE: 讀允許
WE: 寫允許
WP: 在寫或擦除期間,提供防寫
R/B: 讀/忙輸出
1.1.3 定址方式
Samsung K9F1208U0B Nand Flash 片內定址採用26位地址形式。從第0位開始分四次通過I/O0-I/O7進行
傳送,並進行片內定址。具體含義如下:
0-7位:位元組在上半部、下半部及OOB內的偏移地址
8位:值為0代表對一頁內前256個位元組進行定址
值為1代表對一頁內後256個位元組進行定址
9-13位:對頁進行定址 14-25位:對塊進行定址
當傳送地址時,從位0開始
1.1.4 Nand flash主要內設命令詳細介紹
Nand Flash命令執行是通過將命令字送到Nand Flash控制器的命令暫存器來執行。
Nand Flash的命令是分週期執行的,每條命令都有一個或多個執行週期,每個執行週期都有相映程式碼表示該周
期將要執行的動作。
主要命令有:Read 1、Read 2、Read ID、Reset、Page Program、Block Erase、Read Status。
詳細介紹如下:
1. Read 1:
功能:表示將要讀取Nand flash儲存空間中一個頁的前半部分,並且將內建指標定位到前半部分的第一個位元組。
命令程式碼:00h
2. Read 2:
功能:表示將要讀取Nand flash儲存空間中一個頁的後半部分,並且將內建指標定位到後半部分的第一個位元組。
命令程式碼:01h
3. Read ID:
功能:讀取Nand flash晶片的ID號
命令程式碼:90h
4. Reset:
功能:重啟晶片。
命令程式碼:FFh
5. Page Program:
功能:對頁進行程式設計命令, 用於寫操作。
命令程式碼:首先寫入00h(A區)/01h(B區)/05h(C區), 表示寫入那個區; 再寫入80h開始程式設計模式(寫入模式),接
下來寫入地址和資料; 最後寫入10h表示程式設計結束.
6. Block Erase
功能:塊擦除命令。
命令程式碼:首先寫入60h進入擦寫模式,然後輸入塊地址; 接下來寫入D0h, 表示擦寫結束.
7. Read Status
功能:讀取內部狀態暫存器值命令。
命令程式碼:70h
1.2 Nand Flash 控制器工作原理
對Nand Flash儲存晶片進行操作, 必須通過Nand Flash控制器的專用暫存器才能完成。所以,不能對Nand
Flash進行匯流排操作。而Nand Flash的寫操作也必須塊方式進行。對Nand Flash的讀操作可以按位元組讀取。
1.2.1 Nand Flash控制器特性
1. 支援對Nand Flash晶片的讀、檢驗、程式設計控制
2. 如果支援從Nand Flash啟動, 在每次重啟後自動將前Nand Flash的前4KB資料搬運到ARM的內部RAM中
3. 支援ECC校驗
1.2.2 Nand Flash控制器工作原理
Nand Flash控制器在其專用暫存器區(SFR)地址空間中對映有屬於自己的特殊功能暫存器,就是通過將Nand
Flash晶片的內設命令寫到其特殊功能暫存器中,從而實現對Nand flash晶片讀、檢驗和程式設計控制的。特殊功能
暫存器有:NFCONF、NFCMD、NFADDR、NFDATA、NFSTAT、NFECC。寄存詳細說明見下一節。
1.3 Nand flash 控制器中特殊功能暫存器詳細介紹
1. 配置暫存器(NFCONF)功能:用於對Nand Flash控制器的配置狀態進行控制。
在地址空間中地址:0x4E000000,其中:
Bit15:Nand Flash控制器使能位,置0代表禁止Nand Flash控制器,置1代表啟用Nand Flash控制器;
要想訪問Nand Flash晶片上儲存空間,必須啟用Nand Flash控制器。在復位後該位自動置0,因此在初始化時
必須將該位置為1。
Bit12:初始化ECC位,置1為初始化ECC;置0為不初始化ECC。
Bit11:Nand Flash晶片儲存空間使能位,置0代表可以對儲存空間進行操作;置1代表禁止對儲存空
間進行操作。在復位後,該位自動為1。
Bit10-8:TACLS位。根據此設定CLE&ALE的週期。TACLS的值範圍在0-7之間。
Bit6-4、2-0分別為:TWRPH0、TWRPH1位。設定寫操作的訪問週期。其值在0-7之間。
2. 命令暫存器(NFCMD)
功能:用於存放Nand flash晶片內設的操作命令。
在地址空間中地址:0x4E000004,其中:
Bit0-7:存放具體Nand flash晶片內設的命令值。其餘位保留以後用。
3. 地址暫存器(NFADDR)
功能:用於存放用於對Nand flash晶片儲存單元定址的地址值。
在地址空間中地址:0x4E000008,其中:
Bit0-7:用於存放地址值。因為本款Nand flash晶片只有I/O0-7的地址/資料複用引腳且地址是四周
期每次8位送入的,所以這裡只用到8位。其餘位保留待用。
4. 資料暫存器(NFDATA)
功能:Nand flash晶片所有內設命令執行後都會將其值放到該暫存器中。同時,讀出、寫入Nand flash
儲存空間的值也是放到該暫存器。
在地址空間中地址:0x4E00000C,其中:
Bit0-7:用於存放需要讀出和寫入的資料。其餘位保留代用。
5. 狀態暫存器(NFSTAT)
功能:用於檢測Nand flash晶片上次對其儲存空間的操作是否完成。
在地址空間中地址:0x4E000010,其中:
Bit0:置0表示Nand flash晶片正忙於上次對儲存空間的操作;置1表示Nand flash晶片準備好接收新
的對儲存空間操作的請求。
6. ECC校驗暫存器(NFECC)
功能:ECC校驗暫存器
在地址空間中地址:0x4E000014,其中:
Bit0Bit7: ECC0
Bit8Bit15: ECC1
Bit16Bit23: ECC2
1.4 Nand Flash 控制器中的硬體ECC介紹
ECC是用於對儲存器之間傳送資料正確進行校驗的一種演算法,分硬體ECC和軟體ECC演算法兩種,在
S3C2410的Nand Flash 控制器中實現了由硬體電路(ECC 生成器)實現的硬體ECC。1.4.2 ECC生成器工作過程
當寫入資料到Nand flash儲存空間時, ECC生成器會在寫入資料完畢後自動生成ECC碼,將其放入到
ECC0-ECC2。當讀出資料時Nand Flash 同樣會在讀資料完畢後,自動生成ECC碼將其放到ECC0-ECC2當
中。
1.4.3 ECC的運用
當寫入資料時,可以在每頁寫完資料後將產生的ECC碼放入到OOB指定的位置(Byte 6)去,這樣就完成了
ECC碼的儲存。這樣當讀出該頁資料時,將所需資料以及整個OOB讀出,然後將指定位置的ECC碼與讀出數
據後在ECC0-ECC1的實際產生的ECC碼進行對比,如果相等則讀出正確,若不相等則讀取錯誤需要進行重
讀。
2 在ADS下flash燒寫程式
2.1 ADS下flash燒寫程式原理及結構
基本原理:在windows環境下藉助ADS模擬器將在SDRAM中的一段儲存區域中的資料寫到Nand flash存
儲空間中。燒寫程式在縱向上分三層完成:
第一層: 主燒寫函式(完成將在SDRAM中的一段儲存區域中的資料寫到Nand flash儲存空間中);
第二層: 為第一層主燒寫函式提供支援的對Nand flash進行操作的頁讀、寫,塊擦除等函式;
第三層:為第二層提供具體Nand flash控制器中對特殊功能暫存器進行操作的核心函式,該層也是真正的
將資料能夠在SDRAM和Nand flash之間實現傳送的函式。
下面對其三層進行分述:
2.2 第三層實現說明
2.1.1 特殊功能暫存器定義
#define rNFCONF (*(volatile unsigned int *)0x4e000000)
#define rNFCMD (*(volatile unsigned char *)0x4e000004)
#define rNFADDR (*(volatile unsigned char *)0x4e000008)
#define rNFDATA (*(volatile unsigned char *)0x4e00000c)
#define rNFSTAT (*(volatile unsigned int *)0x4e000010)
#define rNFECC (*(volatile unsigned int *)0x4e000014)
#define rNFECC0 (*(volatile unsigned char *)0x4e000014)
#define rNFECC1 (*(volatile unsigned char *)0x4e000015)
#define rNFECC2 (*(volatile unsigned char *)0x4e000016)
2.1.2 操作的函式實現
1. 傳送命令
#define NF_CMD(cmd) {rNFCMD=cmd;}
2. 寫入地址
#define NF_ADDR(addr) {rNFADDR=addr;}
3. Nand Flash晶片選中
#define NF_nFCE_L() {rNFCONF&=~(1<<11);}
4. Nand Flash晶片不選中
#define NF_nFCE_H() {rNFCONF|=(1<<11);}
5. 初始化ECC
#define NF_RSTECC() {rNFCONF|=(1<<12);}
6. 讀資料#define NF_RDDATA() (rNFDATA)
7. 寫資料
#define NF_WRDATA(data) {rNFDATA=data;}
8. 獲取Nand Flash晶片狀態
#define NF_WAITRB() {while(!(rNFSTAT&(1<<0)));}
0/假: 表示Nand Flash晶片忙狀態
1/真:表示Nand Flash已經準備好
2.3 第二層實現說明
2.3.1 Nand Flash 初始化
void NF_Init(void)
{
/* 設定Nand Flash配置暫存器, 每一位的取值見1.3節 */
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
/* 復位外部Nand Flash晶片 */
NF_Reset();
}
2.3.2 Nand flash復位
static void NF_Reset(void)
{
int i;
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0xFF); /* 復位命令 */
for(i=0;i<10;i++); /* 等待tWB = 100ns. */
NF_WAITRB(); /* wait 200~500us; */
NF_nFCE_H(); /* 取消Nand Flash 選中*/
}
2.3.3 獲取Nand flash ID
返回值為Nand flash晶片的ID號
unsigned short NF_CheckId(void)
{
int i;
unsigned short id;
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x90); /* 傳送讀ID命令到Nand Flash晶片 */
NF_ADDR(0x0); /* 指定地址0x0,晶片手冊要求 */
for(i=0;i<10;i++); /* 等待tWB = 100ns. */
id=NF_RDDATA()<<8; /* 廠商ID(K9S1208V:0xec) */
id|=NF_RDDATA(); /* 裝置ID(K9S1208V:0x76) */
NF_nFCE_H(); /* 取消Nand Flash 選中*/
return id;
}2.3.4 Nand flash寫入
以頁為單位寫入.
引數說明:block 塊號
page 頁號
buffer 指向記憶體中待寫入Nand flash中的資料起始位置
返回值: 0:寫錯誤
1:寫成功
static int NF_WritePage(unsigned int block, unsigned int page, unsigned char *buffer)
{
int i;
unsigned int blockPage = (block<<5)+page;
unsigned char *bufPt = buffer;
NF_RSTECC(); /* 初始化 ECC */
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x0); /* 從A區開始寫 */
NF_CMD(0x80); /* 寫第一條命令 */
NF_ADDR(0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
for(i=0;i<512;i++)
{
NF_WRDATA(*bufPt++); /* 寫一個頁512位元組到Nand Flash晶片 */
}
/*
* OOB一共16 Bytes, 每一個位元組存放什麼由程式設計師自己定義, 通常,
* 我們在Byte0Byte2存ECC檢驗碼. Byte6 存放壞塊標誌.
*/
seBuf[0]=rNFECC0; /* 讀取ECC檢驗碼0 */
seBuf[1]=rNFECC1; /* 讀取ECC檢驗碼1 */
seBuf[2]=rNFECC2; /* 讀取ECC檢驗碼2 */
seBuf[5]=0xff; /* 非壞塊標誌 */
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]); /* 寫該頁的OOB資料塊 */
}
NF_CMD(0x10); /* 結束寫命令 */
/* 等待Nand Flash處於準備狀態 */
for(i=0;i<10;i++);
NF_WAITRB();
/* 傳送讀狀態命令給Nand Flash */
NF_CMD(0x70);
for(i=0;i<3;i++);
if (NF_RDDATA()&0x1)
{ /*如果寫有錯, 則標示為壞塊 */
NF_nFCE_H(); /* 取消Nand Flash 選中*/
NF_MarkBadBlock(block);
return 0;
} else { /* 正常退出 */
NF_nFCE_H(); /* 取消Nand Flash 選中*/
return 1;
}
}
2.3.5 Nand flash讀取
引數說明:block:塊號
page:頁號
buffer:指向將要讀取到記憶體中的起始位置
返回值:1:讀成功
0:讀失敗
static int NF_ReadPage(unsigned int block, unsigned int page, unsigned char *buffer)
{
int i;
unsigned int blockPage;
unsigned char ecc0, ecc1, ecc2;
unsigned char *bufPt=buffer;
unsigned char se[16];
page=page&0x1f;
blockPage=(block<<5)+page;
NF_RSTECC(); /* 初始化 ECC */
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x00); /* 從A區開始讀 */
NF_ADDR(0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 等待Nand Flash處於再準備狀態 */
for(i=0;i<10;i++);
NF_WAITRB(); /*等待 tR(max 12us) */
/* 讀整個頁, 512位元組 */
for(i=0;i<512;i++)
{
*bufPt++=NF_RDDATA();
}
/* 讀取ECC碼 */
ecc0=rNFECC0;
ecc1=rNFECC1;
ecc2=rNFECC2;
/* 讀取該頁的OOB塊 */
for(i=0;i<16;i++)
{
se[i]=NF_RDDATA();
}
NF_nFCE_H(); /* 取消Nand Flash 選中*/
/* 校驗ECC碼, 並返回 */
if(ecc0==se[0] && ecc1==se[1] && ecc2==se[2])
return 1;
else
return 0;
}
2.3.6 Nand flash標記壞塊
如果是壞塊, 通過寫OOB塊的Byte6把該塊標記為壞塊。
引數說明:block塊號
返回值:1:ok,成功完成標記。
0:表示寫OOB塊正確.
static int NF_MarkBadBlock(unsigned int block)
{
int i;
unsigned int blockPage=(block<<5);
seBuf[0]=0xff;
seBuf[1]=0xff;
seBuf[2]=0xff;
seBuf[5]=0x44; /* 設定壞塊標記 */
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x50); /* 從C區開始寫 */
NF_CMD(0x80); /* 傳送程式設計命令, 讓Nand Flash處理寫狀態 */
NF_ADDR(0x0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 寫OOB資料塊 */
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]);
}
NF_CMD(0x10); /* 結束寫命令 */
/* 等待NandFlash準備好 */
for(i=0;i<10;i++); /* tWB = 100ns. */
NF_WAITRB(); /*讀NandFlash的寫狀態 */
NF_CMD(0x70);
for(i=0;i<3;i++); /* twhr=60ns */
if (NF_RDDATA()&0x1)
{
NF_nFCE_H(); /* 取消Nand Flash 選中*/
return 0;
} else {
NF_nFCE_H(); /* 取消Nand Flash 選中*/
}
return 1;
}
2.3.7 Nand Flash檢查壞塊
檢查指定塊是否是壞塊.
引數說明:block:塊號
返回值:1:指定塊是壞塊
0:指定塊不是壞塊。
static int NF_IsBadBlock(U32 block)
{
int i;
unsigned int blockPage;
U8 data;
blockPage=(block<<5);
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x50); /* Read OOB資料塊 */
NF_ADDR(517&0xf); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 等待NandFlash準備好 */
for(i=0;i<10;i++); /* wait tWB(100ns) */
NF_WAITRB();
/* 讀取讀出值 */
data=NF_RDDATA();
NF_nFCE_H(); /* 取消Nand Flash 選中*/
/* 如果data不為0xff時, 表示該塊是壞塊 */
if(data != 0xff)
return 1;
else
return 0;
}
2.3.8 擦除指定塊中資料
引數說明:block 塊號
返回值:0:擦除錯誤。(若是壞塊直接返回0;若擦除出現錯誤則標記為壞塊然後返回0) 1:成功擦除。
static int NF_EraseBlock(unsigned int block)
{
unsigned int blockPage=(block<<5);
int i;
/* 如果該塊是壞塊, 則返回 */
if(NF_IsBadBlock(block))
return 0;
NF_nFCE_L(); /* 片選Nand Flash晶片*/
NF_CMD(0x60); /* 設定擦寫模式 */
NF_ADDR(blockPage&0xff); /* A9A16, (Page Address) , 是基於塊擦*/
NF_ADDR((blockPage>>8)&0xff); /* A17A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
NF_CMD(0xd0); /* 傳送擦寫命令, 開始擦寫 */
/* 等待NandFlash準備好 */
for(i=0;i<10;i++); /* tWB(100ns) */
NF_WAITRB();
/* 讀取操作狀態 */
NF_CMD(0x70);
if (NF_RDDATA()&0x1)
{
NF_nFCE_H(); /* 取消Nand Flash 選中*/
NF_MarkBadBlock(block); /* 標記為壞塊 */
return 0;
} else {
NF_nFCE_H(); /* 取消Nand Flash 選中*/
return 1;
}
}
2.4 第一層的實現
2.4.1 NandFlash燒寫主函式說明
引數說明: block 塊號
srcAddress SDRAM中資料起始地址
fileSize 要燒寫的資料長度
返回值: 無
void K9S1208_Program(unsigned int block, unsigned int srcAddress, unsigned int fileSize)
{
int i;
int programError=0;
U32 blockIndex;
U8 *srcPt, *saveSrcPt;
srcPt=(U8 *)srcAddress; /* 檔案起始地址 */
blockIndex = block; /* 塊號 */ while(1)
{
saveSrcPt=srcPt;
/* 如果當前塊是壞塊, 跳過當前塊 */
if(NF_IsBadBlock(blockIndex))
{
blockIndex++; /* 到下一個塊 */
continue;
}
/* 在寫之前, 必須先擦除, 如果擦除不成功, 跳過當前塊 */
if(!NF_EraseBlock(blockIndex))
{
blockIndex++; /* 到下一個塊 */
continue;
}
/* 寫一個塊, 一塊有32頁 */
for(i=0;i<32;i++)
{
/* 寫入一個頁, 如果出錯, 停止寫當前塊 */
if(!NF_WritePage(blockIndex,i,srcPt))
{
programError=1;
break;
}
/* 如果操作正常, 檔案的寫位置加上1頁偏移,到下一頁的起始位置 */
srcPt+=512;
/* 如果寫地址沒有超過檔案長度, 繼續; 超出則終止寫 */
if((U32)srcPt>=(srcAddress+fileSize))
break;
}
/* 如果寫一個塊時, 其中某一頁寫失敗, 則把寫地址恢復寫該塊之前, 並跳過當前塊 */
if(programError==1)
{
blockIndex++;
srcPt=saveSrcPt;
programError=0;
continue;
}
/* 如果寫地址沒有超過檔案長度, 繼續; 超出則終止寫 */
if((U32)srcPt >= (srcAddress+fileSize))
break;
/* 如果正常寫成功, 繼續寫下一個塊 */
blockIndex++;
}
}3 在U-BOOT對Nand Flash的支援
3.1 U-BOOT對從Nand Flash啟動的支援
3.1.1 從Nand Flash啟動U-BOOT的基本原理
1. 前4K的問題
如果S3C2410被配置成從Nand Flash啟動(配置由硬體工程師在電路板設定), S3C2410的Nand Flash控制器
有一個特殊的功能, 在S3C2410上電後, Nand Flash控制器會自動的把Nand Flash上的前4K資料搬移到4K內部
RAM中, 並把0x00000000設定內部RAM的起始地址, CPU從內部RAM的0x00000000位置開始啟動。這個過
程不需要程式干涉。
程式設計師需要完成的工作,是把最核心的啟動程式放在Nand Flash的前4K中。
2. 啟動程式的安排
由於Nand Flash控制器從Nand Flash中搬移到內部RAM的程式碼是有限的,所以, 在啟動程式碼的前4K裡,我
們必須完成S3C2410的核心配置以及把啟動程式碼(UBOOT)剩餘部分搬到RAM中執行。以UBOOT為例, 前4K
完成的主要工作, 見第四部分的2.2節。
3.1.2 支援Nand Flash啟動程式碼說明
首先在include/configs/crane2410.h中加入CONFIG_S3C2410_NAND_BOOT, 如下:
#define CONFIG_S3C2410_NAND_BOOT 1
支援從Nand Flash中啟動.
1. 執行Nand Flash初始化
下面程式碼在cpu/arm920t/start.S中
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START @安裝棧的起始地址
mov fp, #0 @初始化幀指標暫存器
bl nand_reset @跳到復位C函式去執行
...
DW_STACK_START:
.word STACK_BASE+STACK_SIZE4
2. nand_reset C程式碼
下面程式碼被加在/board/crane2410/crane2410.c中
void nand_reset(void)
{
int i;
/* 設定Nand Flash控制器 */
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
/* 給Nand Flash晶片傳送復位命令 */
NF_nFCE_L();
NF_CMD(0xFF);
for(i=0; i<10; i++);
NF_WAITRB(); NF_nFCE_H();
}
3. 從Nand Flash中把UBOOT拷貝到RAM
@read UBOOT from Nand Flash to RAM
ldr r0, =UBOOT_RAM_BASE @ 設定第1個引數: UBOOT在RAM中的起始地址
mov r1, #0x0 @ 設定第2個引數:Nand Flash的起始地址
mov r2, #0x20000 @ 設定第3個引數: UBOOT的長度(128KB)
bl nand_read_whole @ 呼叫nand_read_whole(), 該函式在board/crane2410/crane2410.c中
tst r0, #0x0 @ 如果函式的返回值為0,表示執行成功.
beq ok_nand_read @ 執行記憶體比較
4. 從Nand Flash中把資料讀入到RAM中
int nand_read_whole(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
/* 如果起始地址和長度不是512位元組(1頁)的倍數, 則返回錯誤程式碼 */
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
return 1;
}
/* 啟用Nand Flash */
NF_nFCE_L();
for(i=0; i<10; i++);
i = start_addr;
while(i < start_addr + size) {
/* 讀A區 */
rNFCMD = 0;
/* 寫入讀取地址 */
rNFADDR = i & 0xff;
rNFADDR = (i >> 9) & 0xff;
rNFADDR = (i >> 17) & 0xff;
rNFADDR = (i >> 25) & 0xff;
NF_WAITRB();
/* 讀出一頁(512位元組) */
for(j=0; j < NAND_SECTOR_SIZE; j++, i++) {
*buf = (rNFDATA & 0xff);
buf++;
}
}
/* 停止驅動Nand Flash */
NF_nFCE_H();
return 0;
}5. 校查搬移後的資料
把RAM中的前4K與內部中前4K進行比較, 如果完全相同, 則表示搬移成功.
ok_nand_read:
mov r0, #0x00000000 @內部RAM的起始地址
ldr r1, =UBOOT_RAM_BASE @UBOOT在RAM中的起始地址
mov r2, #0x400 @比較1024次, 每次4位元組, 4 bytes * 1024 = 4Kbytes
go_next: @ 比較1024次, 每次4個位元組
ldr r3, [r0], #4
ldr r4, [r1], #4
teq r3, r4
bne notmatch
subs r2, r2, #4
beq done_nand_read
bne go_next
notmatch:
1:b 1b
done_nand_read:
mov pc, r10
3.2 U-BOOT對Nand Flash命令的支援
在UBOOT下對Nand Flash的支援主要是在命令列下實現對nand flash的操作。對nand flash實現的命令
為:nand info、nand device、nand read、nand write、nand erease、nand bad。
用到的主要資料結構有:struct nand_flash_dev、struct nand_chip。前者包括主要的晶片型號、儲存容量、
裝置ID、I/O匯流排寬度等資訊;後者是具體對nand flash進行操作時用到的資訊。
3.2.1 主要資料結構介紹
1. struct nand_flash_dev資料結構
該資料結構在include/linux/mtd/nand.h中定義,在include/linux/mtd/nand_ids.h中賦初值。
struct nand_flash_dev {
char *name; /* 晶片名稱 */
int manufacture_id; /* 廠商ID */
int model_id; /* 模式ID */
int chipshift; /* Nand Flash地址位數 */
char page256; /* 表明是否時256位元組一頁。1:是;0:否。*/
char pageadrlen; /* 完成一次地址傳送需要往NFADDR中傳送幾次。*/
unsigned long erasesize; /* 一次塊擦除可以擦除多少位元組 */
int bus16; /* 地址線是否是16位,1:是;0:否 */
};
2. struct nand_chip資料結構
該資料結構在include/linux/mtd/nand.h中定義. 該結構體定義出一個Nand Flash裝置陣列:
struct nand_chip nand_dev_desc[CFG_MAX_NAND_DEVICE];
該陣列在nand_probe()中對其進行初始化.
struct nand_chip {
int page_shift; /* Page 地址位數 */
u_char *data_buf; /* 本次讀出的一頁資料 */
u_char *data_cache; /* 讀出的一頁資料 */
int cache_page; /* 上次操作的頁號 */u_char ecc_code_buf[6]; /* ECC校驗碼 */
u_char reserved[2];
char ChipID; /* 晶片ID號 */
struct Nand *chips; /* Nand Flash晶片列表, 表示支援幾個晶片為一個裝置*/
int chipshift;
char* chips_name; /* Nand Flash晶片名稱 */
unsigned long erasesize; /* 塊擦寫的大小 */
unsigned long mfr; /* 廠商ID */
unsigned long id; /* 模式ID */
char* name; /* 裝置名稱 */
int numchips; /* 有幾塊Nand Flash晶片 */
char page256; /* 一頁是256位元組, 還是512位元組 */
char pageadrlen; /* 頁地址的長度 */
unsigned long IO_ADDR; /* 用於對nand flash進行定址的地址值存放處 */
unsigned long totlen; /* Nand Flash總共大小 */
uint oobblock; /* 一頁的大小。本款nand flash為512 */
uint oobsize; /* spare array大小。本款nand flash為16 */
uint eccsize; /* ECC 大小 */
int bus16; /* 地址線是否是16位,1:是;0:否 */
};
3.2.2 支援的命令函式說明
1. nand info/nand device
功能:顯示當前nand flash晶片資訊。
函式呼叫關係如下(按先後順序):
static void nand_print(struct nand_chip *nand) ;
2. nand erase
功能:擦除指定塊上的資料。
函式呼叫關係如下(按先後順序):
int nand_erase(struct nand_chip* nand, size_t ofs, size_t len, int clean);
3. nand bad
功能:顯示壞塊。
函式呼叫關係如下(按先後順序):
static void nand_print_bad(struct nand_chip* nand);
int check_block (struct nand_chip *nand, unsigned long pos);
4. nand read
功能:讀取nand flash資訊到SDRAM。
函式呼叫關係如下(按先後順序):
int nand_rw (struct nand_chip* nand, int cmd,size_t start, size_t len, size_t * retlen, u_char * buf);
static int nand_read_ecc(struct nand_chip *nand, size_t start, size_t len,
size_t * retlen, u_char *buf, u_char *ecc_code);
static void NanD_ReadBuf (struct nand_chip *nand, u_char * data_buf, int cntr);
READ_NAND(adr);
5. nand write
功能:從SDRAM寫資料到nand flash中。
函式呼叫關係如下(按先後順序):int nand_rw (struct nand_chip* nand, int cmd,size_t start, size_t len, size_t * retlen, u_char * buf);
static int nand_write_ecc (struct nand_chip* nand, size_t to, size_t len,
size_t * retlen, const u_char * buf, u_char * ecc_code);
static int nand_write_page (struct nand_chip *nand, int page, int col, int last, u_char * ecc_code);
WRITE_NAND(d , adr);
3.2.3 U-BOOT支援Nand Flash命令移植說明
1. 設定配置選項
在CONFIG_COMMANDS中, 開啟CFG_CMD_NAND選項.
#define CONFIG_COMMANDS \
(CONFIG_CMD_DFL | \
CFG_CMD_CACHE | \
CFG_CMD_NAND | \
/*CFG_CMD_EEPROM |*/ \
/*CFG_CMD_I2C |*/ \
/*CFG_CMD_USB |*/ \
CFG_CMD_PING | \
CFG_CMD_REGINFO | \
CFG_CMD_DATE | \
CFG_CMD_ELF)
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
#define CFG_NAND_BASE 0x4E000000 /* Nand Flash控制器在SFR區中起始暫存器地址 */
#define CFG_MAX_NAND_DEVICE 1 /* 支援的最在Nand Flash資料 */
#define SECTORSIZE 512 /* 1頁的大小 */
#define NAND_SECTOR_SIZE SECTORSIZE
#define NAND_BLOCK_MASK (NAND_SECTOR_SIZE – 1) /* 頁掩碼 */
#define ADDR_COLUMN 1 /* 一個位元組的Column地址 */
#define ADDR_PAGE 3 /* 3位元組的頁塊地址, A9A25*/
#define ADDR_COLUMN_PAGE 4 /* 總共4位元組的頁塊地址 */
#define NAND_ChipID_UNKNOWN 0x00 /* 未知晶片的ID號 */
#define NAND_MAX_FLOORS 1
#define NAND_MAX_CHIPS 1
/* Nand Flash命令層底層介面函式 */
#define WRITE_NAND_COMMAND(d, adr) do {rNFCMD = d;} while(0)
#define WRITE_NAND_ADDRESS(d, adr) do {rNFADDR = d;} while(0)
#define WRITE_NAND(d, adr) do {rNFDATA = d;} while(0)
#define READ_NAND(adr) (rNFDATA)
#define NAND_WAIT_READY(nand) {while(!(rNFSTAT&(1<<0)));}
#define NAND_DISABLE_CE(nand) {rNFCONF |= (1<<11);}
#define NAND_ENABLE_CE(nand) {rNFCONF &= ~(1<<11);}
/* 下面一組操作對Nand Flash無效 */
#define NAND_CTL_CLRALE(nandptr)
#define NAND_CTL_SETALE(nandptr)
#define NAND_CTL_CLRCLE(nandptr)
#define NAND_CTL_SETCLE(nandptr)/* 允許Nand Flash寫校驗 */
#define CONFIG_MTD_NAND_VERIFY_WRITE 1
#endif /* CONFIG_COMMANDS & CFG_CMD_NAND*/
2. 加入自己的Nand Flash晶片型號
在include/linux/mtd/ nand_ids.h中的對如下結構體賦值進行修改:
static struct nand_flash_dev nand_flash_ids[] = {
......
{"Samsung K9F1208U0B", NAND_MFR_SAMSUNG, 0x76, 26, 0, 4, 0x4000, 0},
.......
}
這樣對於該款Nand Flash晶片的操作才能正確執行。
3. 編寫自己的Nand Flash初始化函式
在board/crane2410/crane2410.c中加入nand_init()函式.
void nand_init(void)
{
/* 初始化Nand Flash控制器, 以及Nand Flash 晶片 */
nand_reset();
/* 呼叫nand_probe()來檢測晶片型別 */
printf ("%4lu MB\n", nand_probe(CFG_NAND_BASE) >> 20);
}
該函式在啟動時被start_armboot()呼叫.
4 在Linux對Nand Flash的支援
4.1 Linux下Nand Flash呼叫關係
4.1.1 Nand Flash裝置新增時資料結構包含關係
struct mtd_partition partition_info[]
--> struct s3c2410_nand_set nandset
--> struct s3c2410_plat form_nand superlpplatfrom
--> struct platform_device s3c_device_nand
在該資料結構的name欄位的初始化值"s3c2410-nand",必須與Nand Flash裝置驅動註冊時
struct device_driver結構中的name欄位相同,因為platfrom bus是依靠名字來匹配的.
--> struct platform_device *smdk2410_devices[]
4.1.2 Nand Flash設備註冊時資料結構包含關係
struct device_driver s3c2410_n and_driver
-->struct device *dev
該資料構由系統分配.
-->struct platform_device *pdev
-->struct s3c2410_plat form_nand *plat
-->struct s3c2410_nand_set nset
-->struct mtd_partition
4.1.3 當發生系統呼叫時資料結構呼叫關係
struct mtd_info它的*priv指向chip
-->struct nand_chip
它的*priv指向nmtd
-->struct s3c2410_nand_mtd
它是s3c2410_nand_info的一個欄位
-->s3c2410_nand_info
它被設為Nand Flash裝置驅動的私有資料結構,在Nand Flash裝置驅動註冊時分配空間.
-->struct device
4.2 Linux下Nand Flash驅動主要資料結構說明
4.2.1 s3c2410專有資料結構
1. s3c2410_nand_set
struct s3c2410_nand_set {
int n r_chips; /* 晶片的數目 */
int n r_partitions; /* 分割槽的數目 */
char *n ame; /* 集合名稱 */
int nr_map; /* 可選, 底層邏輯到物理的晶片數目 */
struct mtd_partition partitions; /* 分割槽列表 */
};
2. s3c2410_platform_and
struct s3c2410_platform_nand {
/* timing information for controller, all times in nanoseconds */
int tacls; /* 從CLE/ALE有效到 nWE/nOE的時間 */
int twrph0; /* nWE/nOE的有效時間 */
int twrph1; /* 從釋放CLE/ALE到nWE/nOE不活動的時間 */
int nr_sets; /* 集合數目 */
struct s3c2410_nand_set sets; /* 集合列表 */
/* 根據晶片編號選擇有效集合 */
void (*select_chip)(struct s3c2410_nand_set , int chip);
};
3. s3c2410_nand_mtd
在drivers/mtd/nand/s3c2410.c中,
struct s3c2410_nand_mtd {
struct mtd_info mt d; /* MTD 資訊 */
struct nand_chip ch ip; /* nand flash 晶片資訊 */
struct s3c2410_nand_set set; /* nand flash 集合 */
struct s3c2410_nand_info *info; /* nand flash 資訊 */
int scan _res;
};
4. s3c2410_nand_info
struct s3c2410_nand_info {
/* mtd info */
struct nand_hw_control co ntroller; /* 硬體控制器 */
struct s3c2410_nand_mtd *mt ds; /* MTD 裝置表 */
struct s3c2410_platform_nand platform; /* Nand 裝置的平臺 */ /* device info */
struct device *device; /* 裝置指標 */
struct resource *area; /* 資源指標 */
struct clk *clk; /* N and Flash 時鐘 */
void __iomem *reg s; /* 暫存器基地址(map後的邏輯地址) */
int mt d_count; /* MTD的數目 */
unsigned char is_s3c2440;
};
5. struct clk
在arch/arm/machs3c2410/clock.h中
struct clk {
struct list_head list ; /* clock 列表結點 */
struct module *o wner; /* 所屬模組 */
struct clk *paren t; /* 父結點 */
const char *n ame; /* 名稱 */
int id; /* 編號 */
atomic_t used; /* 使用者計數 */
unsigned long rate; /* 時鐘速率 */
unsigned long ctrlbit; /* 控制位 */
int (*en able)(struct clk *, int enable); /* Clock開啟方法 */
};
4.2.2 Linux 通用資料結構說明
1. device_driver
include/linux/device.h
struct device_driver {
const char * n ame; /* 驅動名稱 */
struct bus_type * b us; /* 匯流排型別 */
struct completion unloaded; /* 解除安裝事件通知機制 */
struct kobject kobj; /* sys中的物件 */
struct klist klist _devices; /* 裝置列表 */
struct klist_node knode_bus; /* 匯流排結點列表 */
struct module * o wner;/* 所有者 */
/* 裝置驅動通用方法 */
int (*probe) (struct device * dev) ; /* 探測裝置 */
int (*remove) (struct device * dev) ; /* 移除裝置 */
void (*shutdown) (struct device * dev) ; /* 關閉裝置 */
/* 掛起裝置 */
int (*suspend) (struct device * dev, pm_messag e_t state, u32 level) ;
int (*resume) (struct device * dev, u32 level) ; /* 恢復 */
};
2. platform_device
include/linux/device.h
struct platform_device {
const char * name; /* 名稱 */
u32 id; /* 裝置編號, -1表示不支援同類多個裝置 */
struct device dev; /* 裝置 */
u32 n um_resources; /* 資源數 */
struct resource * resource; /* 資源列表 */};
3. resource
struct resource {
const char name; /* 資源名稱 */
unsigned long start, end; /* 開始位置和結束位置 */
unsigned long flags; /* 資源型別 */
/* 資源在資源樹中的父親,兄弟和孩子 */
struct resource *parent, *sibling, *child;
};
4. device
include/linux/device.h
struct device {
struct klist klist _children; /* 在裝置列表中的孩子列表 */
struct klist_node knode_parent; /* 兄弟結點 */
struct klist_node knode_driver; /* 驅動結點 */
struct klist_node knode_bus; /* 匯流排結點 */
struct device parent; /* 父親 */
struct kobject kobj; /* sys結點 */
char bus_id[BUS_ID_SIZE];
struct semaphore sem; /* 同步驅動的訊號量 */
struct bus_type * bus; /* 匯流排型別 */
struct device_driver *driver; /* 裝置驅動 */
void *driver_dat a; /* 驅動的私有資料 */
void *plat form_data; /* 平臺指定的資料,為device核心驅動保留 */
void *firmw are_data; /* 韌體指定的資料,為device核心驅動保留 */
struct dev_pm_info power; /* 裝置電源管理資訊 */
u64 *dma_mask; /* DMA掩碼 */
u64 co herent_dma_mask;
struct list_head dma_pools; /* DMA緩衝池 */
struct dma_coherent_mem *dma_mem; /* 連續DMA記憶體的起始位置 */
void (*release) (struct device * dev) ; /* 釋放設定方法 */
};
5. nand_hw_control
include/linux/mtd/nand.h
struct nand_hw_control {
spinlock_t lock; /* 自旋鎖,用於硬體控制 */
struct nand_chip *active; /* 正在處理MTD裝置 */
wait_queue_head_t wq; /* 等待佇列 */
};
6. nand_chip
include/linux/mtd/nand.h
struct nand_chip {
void __iomem *IO_ADDR_R; /* 讀地址 */
void __iomem *IO_ADDR_W; /* 寫地址 */ /* 位元組操作 */
u_char (*read_b yte)(struct mtd_info *mtd); /* 讀一個位元組 */
void (*w rite_byte)(struct mtd_info *mtd, u_char byte); /* 寫一個位元組 */
/* 雙位元組操作 */
u16 (*read_w ord)(struct mtd_info mtd); /* 讀一個字 */
void (*w rite_word)(struct mtd_info *mtd, u16 word); /* 寫一個字 */
/* buffer操作 */
void (*w rite_buf)(struct mtd_info *mtd, const u_char *buf, int len);
void (*read_b uf)(struct mtd_info *mtd, u_char *buf, int len);
int (*verify_b uf)(struct mtd_info *mtd, const u_char *buf, int len);
/* 選擇一個操作晶片 */
void (*select _chip)(struct mtd_info *mtd, int chip);
/* 壞塊檢查操作 */
int (*b lock_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
/* 壞塊標記操作 */
int (*b lock_markbad)(struct mtd_info *mtd, loff_t ofs);
/* 硬體控制操作 */
void (*h wcontrol)(struct mtd_info *mtd, int cmd);
/* 裝置準備操作 */
int (*dev_ready) (struct mtd_info *mtd);
/* 命令傳送操作 */
void (*cmdfun c)(struct mtd_info *mtd, unsigned command, int column, int
page_addr);
/* 等待命令完成 */
int (*w aitfunc)(struct mtd_info *mtd, struct nand_chip *this, int state);
/* 計算ECC碼操作 */
int (*calculat e_ecc)(struct mtd_info *mtd, const u_char *dat, u_char
*ecc_code);
/* 資料糾錯操作 */
int (*co rrect_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc,
u_char *calc_ecc) ;
/* 開啟硬體ECC */
void (*en able_hwecc)(struct mtd_info *mtd, int mode);
/* 擦除操作 */
void (*erase_cmd) (struct mtd_info *mtd, int page);
/* 檢查壞塊表 */
int (*scan _bbt)(struct mtd_info *mtd);
int eccmo de; /* ECC模式 */
int eccsize; /* E CC 計算時使用的位元組數 */
int eccb ytes; /* ECC 碼的位元組數 */
int eccst eps; /* ECC 碼計算的步驟數 */
int ch ip_delay; /* 晶片的延遲時間 */
spinlock_t chip_lock; /* 晶片訪問的自旋鎖 */
wait_queue_head_t wq; /* 晶片訪問的等待佇列 */
nand_state_t state; /* Nand Flash狀態 */
int pag e_shift; /* 頁右移的位數,即column地址位數 */
int ph ys_erase_shift; /* 塊右移的位數, 即column和頁一共的地址位數 */
int b bt_erase_shift; /* 壞塊頁表的位數 */
int ch ip_shift; /* 該晶片總共的地址位數 */
u_char *dat a_buf; /* 資料緩衝區 */
u_char *o ob_buf; /* oob緩衝區 */
int o obdirty; /* oob緩衝區是否需要重新初始化 */
u_char *dat a_poi; /* 資料緩衝區指標 */
unsigned int options; /* 晶片專有選項 */
int b adblockpos;/* 壞塊標示位元組在OOB中的位置 */
int n umchips; /* 晶片的個數 */ unsigned long chipsize; /* 在多個晶片組中, 一個晶片的大小 */
int pag emask; /* 每個晶片頁數的遮蔽字, 通過它取出每個晶片包含多少個頁 */
int pag ebuf; /* 在頁緩衝區中的頁號 */
struct nand_oobinfo *autooob; /* oob資訊 */
uint8_t *bbt; /* 壞塊頁表 */
struct nand_bbt_descr *bbt_td; /* 壞塊表描述 */
struct nand_bbt_descr *bbt_md; /* 壞塊表映象描述 */
struct nand_bbt_descr *badblock_pattern; /* 壞塊檢測模板 */
struct nand_hw_control *controller; /* 硬體控制 */
void *priv; /* 私有資料結構 */
/* 進行附加錯誤檢查 */
int (*errst at)(struct mtd_info *mtd, struct nand_chip *this, int state, int
status, int page);
};
7. mtd_info
include/linux/mtd/mtd.h
struct mtd_info {
u_char type; /* 裝置型別 */
u_int32_t flags; /* 裝置標誌位組 */
u_int32_t size; /* 總共裝置的大小 */
u_int32_t erasesize; /* 擦除塊的大小 */
u_int32_t oobblock; /* OOB塊的大小,如:512個位元組有一個OOB */
u_int32_t oobsize; /* OOB資料的大小,如:一個OOB塊有16個位元組 */
u_int32_t ecctype; /* ECC校驗的型別 */
u_int32_t eccsize; /* E CC碼的大小 */
char *name; /* 裝置名稱 */
int index; /* 裝置編號 */
/* oobinfo資訊,它可以通過 MEMSETOOBINFO ioctl命令來設定 */
struct nand_oobinfo oobinfo;
u_int32_t oobavail; /* OOB區的有效位元組數,為檔案系統提供 */
/* 資料擦除邊界資訊 */
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
u_int32_t bank_size; /* 保留 */
/* 擦除操作 */
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
/* 指向某個執行程式碼位置 */
int (*point) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char **mtdbuf);
/* 取消指向 */
void (*unpoint) (struct mtd_info *mtd, u_char * addr, lo ff_t from, size_t len);
/* 讀/寫操作 */
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
/* 帶ECC碼的讀/寫操作 */
int (*read_ecc) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
int (*write_ecc) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel); /* 帶OOB碼的讀/寫操作 */
int (*read_oob) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
u_char *buf);
int (*write_oob) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf);
/* 提供訪問保護暫存器區的方法 */
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_user_pro t_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
/* 提供readv和writev方法 */
int (*readv) (struct mtd_info *mtd, struct kvec *vecs, un signed long count,
loff_t from, size_t *retlen);
int (*readv_ecc) (struct mtd_info *mtd, struct kvec *vecs, un signed long count,
loff_t from, size_t *retlen, u_char *eccbuf,
struct nand_oobinfo *oobsel);
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen);
int (*writev_ecc) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen,
u_char *eccbuf, struct nand_oobinfo *oobsel);
/* 同步操作 */
void (*sync) (struct mtd_info *mtd);
/* 晶片級支援的加/解鎖操作 */
int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
/* 電源管理操作 */
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
/* 壞塊管理操作 */
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
/* 重啟前的通知事件 */
struct notifier_block reboot_notifier;
void *priv; /* 私有資料結構 */
struct module *owner; /* 模組所有者 */
int usecount; /* 使用次數 */
};
4.3 Linux下Nand Flash驅動說明4.3.1 註冊driver_register
通過module_init(s3c2410_nand_init);註冊Nand Flash驅動. 在 s3c2410_nand_init ()中通過driver_register()註冊
s3c2410_nand_driver驅動程式,如下所示:
static struct device_driver s3c2410_n and_driver = {
.name = "s3c2410-n and",
.bus = &plat form_bus_type, /* 在drivers/base/platform.c中定義 */
.probe = s3c2410_n and_probe,
.remove = s3c2410_n and_remove,
};
4.3.2 探測裝置probe
在註冊的Nand Flash驅動程式中, probe方法為s3c2410_nand_probe(). s3c2410_nand_probe()再呼叫
s3c24xx_nand_probe(). 在該函式中, 把*info作為Nand Flash驅動的私有資料結構, 並通過dev_set_drvdata(dev,
info)把*info儲存在*device的*driver_data欄位中.然後通過clk_get(dev, "nand")獲取Nand Flash的時鐘資
源, clk_use(in fo->clk)增加時鐘資源的使用計數, clk_enable(info->clk)開啟資源.填寫*info的其它欄位,
其中包括:
1. 通過request_mem_region()為Nand Flash暫存器區申請I/O記憶體地址空間區,並通過ioremap()把它對映到虛
擬地址空間.
2. 呼叫s3c2410_nand_inithw()初始化Nand Flash控制器.
3. 為mtd裝置分配裝置資訊的儲存空間.
4. 對當前mtd裝置,呼叫s3c2410_nand_init_chip()進行初始化.
5. 對當前mtd裝置, 呼叫nand_scan()檢測Nand Flash晶片, nand_scan()函式在drivers/mtd/nand/nand_base.c中
定義.該函式的作用是初始化struct nand_chip中一些方法, 並從Nand Flash中讀取晶片ID, 並初始化struct
mtd_info中的方法.
6. 對當前mtd裝置,加入其分割槽資訊.
7. 如果還有更多mtd裝置,到4執行.
4.3.3 初始化Nand Flash控制器
s3c2410_nand_inithw()函式會初始化Nand Flash控制器, 通過設定Nand Flash控制暫存器(S3C2410_NFCONF)來
完成, 這裡最重要的是根據S3C2410的PCLK計算出tacls, twrph0以及twrph1值.
4.3.4 移除裝置
s3c2410_nand_remove()當裝置被移除時,被device核心驅動呼叫.它完成的主要工作如下:
1. 把*device的*driver_data欄位置空.
2. 釋放mtd裝置資訊.
3. 釋放clk資源.
4. 通過iounmap()取消映地址空間.
5. 釋放申請的I/O記憶體資源.
6. 釋放裝置私有資料*info的空間.
4.3.5 Nand Flash晶片初始化
s3c2410_nand_init_chip()初始化struct nand_chip中的一些主要欄位以及方法.其中主要包括的方法有:
1. s3c2410_nand_hwcontrol(); 硬體控制
2. s3c2410_nand_devready(); 裝置是否準備好
3. s3c2410_nand_write_buf(); 寫一個buffer到nand flash
4. s3c2410_nand_read_buf(); 讀一個buffer到nand flash
5. s3c2410_nand_select_chip(); 選擇操作晶片
如果支援ECC硬體校驗,還設定如下方法: 1. s3c2410_nand_correct_data(); 通過ECC碼校正資料
2. s3c2410_nand_enable_hwecc(); 開啟硬體ECC檢查
3. s3c2410_nand_calculate_ecc(); 計算ECC碼
4.3.6 讀Nand Flash
當對nand flash的裝置檔案(nand flash在/dev下對應的檔案)執行系統呼叫read(),或在某個檔案系統中對該
裝置進行讀操作時. 會呼叫struct mtd_info中的read方法,他們預設呼叫函式為nand_read(),在
drivers/mtd/nand/nand_base.c中定義.nand_read()呼叫nand_do_read_ecc(),執行讀操作. 在
nand_do_read_ecc()函式中,主要完成如下幾項工作:
1. 會呼叫在nand flash驅動中對struct nand_chip過載的select_chip方法,即
s3c2410_nand_select_chip()選擇要操作的MTD晶片.
2. 會呼叫在struct nand_chip中系統預設的方法cmdfunc傳送讀命令到nand flash.
3. 會呼叫在nand flash驅動中對struct nand_chip過載的read_buf(),即s3c2410_nand_read_buf()
從Nand Flash的控制器的資料暫存器中讀出資料.
4.