我用linux2.6.22內核,2440板子,先從啟動信息入手。
內核啟動信息,NAND部分:
S3C24XX NAND Driver, (c) 2004 Simtec Electronics s3c2440-nand s3c2440-nand: Tacls=3, 30ns Twrph0=7 70ns, Twrph1=3 30ns NAND device: Manufacturer ID: 0xec, Chip ID: 0xda (Samsung NAND 256MiB 3,3V 8-bit) Scanning device for bad blocks Bad eraseblock 256 at 0x02000000 Bad eraseblock 257 at 0x02020000 Bad eraseblock 319 at 0x027e0000 Bad eraseblock 606 at 0x04bc0000 Bad eraseblock 608 at 0x04c00000 Creating 4 MTD partitions on "NAND 256MiB 3,3V 8-bit": 0x00000000-0x00040000 : "bootloader" 0x00040000-0x00060000 : "params" 0x00060000-0x00260000 : "kernel" 0x00260000-0x10000000 : "root"
第一行,在driver/mtd/nand/s3c2410.c中第910行,s3c2410_nand_init函數:
printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");
第二行,同一文件,第212行,s3c2410_nand_inithw函數:
dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n", tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate));
第三行,在driver/mtd/nand/nand_base.c中第2346行,
printk(KERN_INFO "NAND device: Manufacturer ID:" " 0x%02x, Chip ID: 0x%02x (%s %s)\n", *maf_id, dev_id, nand_manuf_ids[maf_idx].name, type->name);
第四行,在driver/mtd/nand/nand_bbt.c中第380行,creat_bbt函數:
Printk(KERN INFO " Scanning device for bad blocks \n");
第五行,在driver/mtd/mtdpart.c中第340行,add_mtd_partitions函數:
printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
下面幾行,是flash分區表,也在mtdpart.c同一函數中,第430行:
printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset, slave->offset + slave->mtd.size, slave->mtd.name);
具體分析
MTD體系結構:
在linux中提供了MTD(Memory Technology Device,內存技術設備)系統來建立Flash針對linux的統一、抽象的接口
引入MTD後,linux系統中的Flash設備驅動及接口可分為4層:
設備節點
MTD設備層
MTD原始設備層
硬件驅動層
下面具體分析
硬件驅動層:Flash硬件驅動層負責底層硬件設備實際的讀、寫、擦除,Linux MTD設備的NAND型Flash驅動位於driver/mtd/nand子目錄下
s3c2410對應的nand Flash驅動為s3c2410.c
MTD原始設備層:MTD原始設備層由兩部分構成,一部分是MTD原始設備的通用代碼,另一部分是各個特定Flash的數據,比如分區
主要構成的文件有:
drivers/mtd/mtdcore.c 支持mtd字符設備
driver/mtd/mtdpart.c 支持mtd塊設備
MTD設備層:基於MTD原始設備,Linux系統可以定義出MTD的塊設備(主設備號31) 和字符設備(設備號90),構成MTD設備層
簡單的說就是:使用一個mtd層來作為具體的硬件設備驅動和上層文件系統的橋梁。mtd給出了系統中所有mtd設備(nand,nor,diskonchip)的統一組織方式。
mtd層用一個數組struct mtd_info *mtd_table[MAX_MTD_DEVICES]保存系統中所有的設備,mtd設備利用struct mtd_info 這個結構來描述,該結構中描述了存儲設備的基本信息和具體操作所需要的內核函數,mtd系統的那個機制主要就是圍繞這個結構來實現的。結構體在include/linux/mtd/mtd.h中定義:
struct mtd_info { u_char type; //MTD 設備類型 u_int32_t flags; //MTD設備屬性標志 u_int32_t size; //標示了這個mtd設備的大小 u_int32_t erasesize; //MTD設備的擦除單元大小,對於NandFlash來說就是Block的大小 u_int32_t oobblock; //oob區在頁內的位置,對於512字節一頁的nand來說是512 u_int32_t oobsize; //oob區的大小,對於512字節一頁的nand來說是16 u_int32_t ecctype; //ecc校驗類型 u_int32_t eccsize; //ecc的大小 char *name; //設備的名字 int index; //設備在MTD列表中的位置 struct nand_oobinfo oobinfo; //oob區的信息,包括是否使用ecc,ecc的大小 //以下是關於mtd的一些讀寫函數,將在nand_base中的nand_scan中重載 int (*erase) int (*read) int (*write) int (*read_ecc) int (*write_ecc) int (*read_oob) int (*read_oob) void *priv;//設備私有數據指針,對於NandFlash來說指nand芯片的結構
下面看nand_chip結構,在include/linux/mtd/nand.h中定義:
struct nand_chip { void __iomem *IO_ADDR_R; //這是nandflash的讀寫寄存器 void __iomem *IO_ADDR_W; //以下都是nandflash的操作函數,這些函數將根據相應的配置進行重載 u_char (*read_byte)(struct mtd_info *mtd); void (*write_byte)(struct mtd_info *mtd, u_char byte); u16 (*read_word)(struct mtd_info *mtd); void (*write_word)(struct mtd_info *mtd, u16 word); void (*write_buf)(struct mtd_info *mtd, const u_char *buf, int len); void (*read_buf)(struct mtd_info *mtd, u_char *buf, int len); int (*verify_buf)(struct mtd_info *mtd, const u_char *buf, int len); void (*select_chip)(struct mtd_info *mtd, int chip); int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip); int (*block_markbad)(struct mtd_info *mtd, loff_t ofs); void (*hwcontrol)(struct mtd_info *mtd, int cmd); int (*dev_ready)(struct mtd_info *mtd); void (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr); int (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this, int state); int (*calculate_ecc)(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code); int (*correct_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc); void (*enable_hwecc)(struct mtd_info *mtd, int mode); void (*erase_cmd)(struct mtd_info *mtd, int page); int (*scan_bbt)(struct mtd_info *mtd); int eccmode; //ecc的校驗模式(軟件,硬件) int chip_delay; //芯片時序延遲參數 int page_shift; //頁偏移,對於512B/頁的,一般是9 u_char *data_buf; //數據緩存區
跟NAND操作相關的函數:
1、 nand_base.c:
定義了NAND驅動中對NAND芯片最基本的操作函數和操作流程,如擦除、讀寫page、讀寫oob等。當然這些函數都只是進行一些常規的操作,若你的系統在對NAND操作時有一些特殊的動作,則需要在你自己的驅動代碼中進行定義。
2、 nand_bbt.c:
定義了NAND驅動中與壞塊管理有關的函數和結構體。
3、 nand_ids.c:
定義了兩個全局類型的結構體:struct nand_flash_dev nand_flash_ids[ ]和struct nand_manufacturers nand_manuf_ids[ ]。其中前者定義了一些NAND芯片的類型,後者定義了NAND芯片的幾個廠商。NAND芯片的ID至少包含兩項內容:廠商ID和廠商為自己的NAND芯片定義的芯片ID。當NAND加載時會找這兩個結構體,讀出ID,如果找不到,就會加載失敗。
4、 nand_ecc.c:
定義了NAND驅動中與softeware ECC有關的函數和結構體,若你的系統支持hardware ECC,且不需要software ECC,則該文件也不需理會。
我們需要關心的是/nand/s3c2410,這個文件實現的是s3c2410/2440nandflash控制器最基本的硬件操作,讀寫擦除操作由上層函數完成。
s3c2410.c分析:
首先看一下要用到的結構體的注冊:
struct s3c2410_nand_mtd { struct mtd_info mtd; //mtd_info的結構體 struct nand_chip chip; //nand_chip的結構體 struct s3c2410_nand_set *set; struct s3c2410_nand_info *info; int scan_res; }; enum s3c_cpu_type { //用來枚舉CPU類型 TYPE_S3C2410, TYPE_S3C2412, TYPE_S3C2440, }; struct s3c2410_nand_info { /* mtd info */ struct nand_hw_control controller; struct s3c2410_nand_mtd *mtds; struct s3c2410_platform_nand *platform; /* device info */ struct device *device; struct resource *area; struct clk *clk; void __iomem *regs; void __iomem *sel_reg; int sel_bit; int mtd_count; unsigned long save_nfconf; enum s3c_cpu_type cpu_type; };
設備的注冊:
static int __init s3c2410_nand_init(void) { printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n"); platform_driver_register(&s3c2412_nand_driver); platform_driver_register(&s3c2440_nand_driver); return platform_driver_register(&s3c2410_nand_driver); } platform_driver_register向內核注冊設備,同時支持這三種CPU。 &s3c2440_nand_driver是一個platform_driver類型的結構體: static struct platform_driver s3c2440_nand_driver = { .probe = s3c2440_nand_probe, .remove = s3c2410_nand_remove, .suspend = s3c24xx_nand_suspend, .resume = s3c24xx_nand_resume, .driver = { .name = "s3c2440-nand", .owner = THIS_MODULE, }, };
最主要的函數就是s3c2440_nand_probe,(調用s3c24XX_nand_probe),完成對nand設備的探測,
static int s3c24xx_nand_probe(struct platform_device *pdev, enum s3c_cpu_type cpu_type) { /*主要完成一些硬件的初始化,其中調用函數:*/ <b>s3c2410_nand_init_chip</b>(info, nmtd, sets); /*init_chip結束後,調用nand_scan完成對flash的探測及mtd_info讀寫函數的賦值*/ nmtd->scan_res = <b>nand_scan</b>(&nmtd->mtd, (sets) ? sets->nr_chips : 1); if (nmtd->scan_res == 0) { <b>s3c2410_nand_add_partition</b>(info, nmtd, sets); } }
Nand_scan是在初始化nand的時候對nand進行的一步非常好重要的操作,在nand_scan中會對我們所寫的關於特定芯片的讀寫函數重載到nand_chip結構中去,並會將mtd_info結構體中的函數用nand的函數來重載,實現了mtd到底層驅動的聯系。
並且在nand_scan函數中會通過讀取nand芯片的設備號和廠家號自動在芯片列表中尋找相應的型號和參數,並將其注冊進去。
static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *nmtd, struct s3c2410_nand_set *set) { struct nand_chip *chip = &nmtd->chip; void __iomem *regs = info->regs; /*以下都是對chip賦值,對應nand_chip中的函數*/ chip->write_buf = s3c2410_nand_write_buf; //寫buf chip->read_buf = s3c2410_nand_read_buf; //讀buf chip->select_chip = s3c2410_nand_select_chip;//片選 chip->chip_delay = 50; chip->priv = nmtd; chip->options = 0; chip->controller = &info->controller; //?? switch (info->cpu_type) { case TYPE_S3C2440: chip->IO_ADDR_W = regs + S3C2440_NFDATA; //數據寄存器 info->sel_reg = regs + S3C2440_NFCONT; //控制寄存器 info->sel_bit = S3C2440_NFCONT_nFCE; chip->cmd_ctrl = s3c2440_nand_hwcontrol; //硬件控制 chip->dev_ready = s3c2440_nand_devready; //設備就緒 chip->read_buf = s3c2440_nand_read_buf; //讀buf chip->write_buf = s3c2440_nand_write_buf;//寫buf break; } chip->IO_ADDR_R = chip->IO_ADDR_W; //讀寫寄存器都是同一個 nmtd->info = info; nmtd->mtd.priv = chip; //私有數據指針指向chip nmtd->mtd.owner = THIS_MODULE; nmtd->set = set; /*後面是和ECC校驗有關的,省略*/ }
初始化後,實現對nand的基本硬件操作就可以了,包括以下函數:
s3c2410_nand_inithw //初始化硬件,在probe中調用
s3c2410_nand_select_chip //片選
s3c2440_nand_hwcontrol //硬件控制,其實就是片選
s3c2440_nand_devready //設備就緒
s3c2440_nand_enable_hwecc //使能硬件ECC校驗
s3c2440_nand_calculate_ecc //計算ECC
s3c2440_nand_read_buf s3c2440_nand_write_buf
注冊nand設備到MTD原始設備層:(這個函數由probe調用)
#ifdef CONFIG_MTD_PARTITIONS //如果定義了MTD分區 static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *mtd, struct s3c2410_nand_set *set) { if (set == NULL) return add_mtd_device(&mtd->mtd); if (set->nr_partitions > 0 && set->partitions != NULL) { return add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions); } return add_mtd_device(&mtd->mtd); } #else
注冊設備用這兩個函數:
add_mtd_device //如果nand整體不分區,用這個,
//該函數在mtdcore.c中實現
add_mtd_partitions //如果nand是分區結構,用這個,
//該函數在mtdpart.c中實現
同樣,注銷設備也有兩個函數:
del_mtd_device
del_mtd_partitions
NandFlash還有一個分區表結構體,mtd_partition,這個是在arch/arm/plat-s3c24XX/common-smdk.c中定義的。
static struct mtd_partition smdk_default_nand_part[] = { [0] = { .name = "boot", .size = 0x00040000, .offset = 0, }, [1] = { .name = "kernel", .offset = 0x0004C000, .size = 0x00200000, }, [2] = { .name = "yaffs2", .offset = 0x0024C000, .size = 0x03DB0000, }, };
記錄了當前的nand flash有幾個分區,每個分區的名字,大小,偏移量是多少
系統就是依靠這些分區表找到各個文件系統的
這些分區表nand中的文件系統沒有必然關系,分區表只是把flash分成不同的部分
如果自己編寫一個nandflash驅動,只需要填充這三個結構體:
Mtd_info nand_chip mtd_partition
並實現對物理設備的控制,上層的驅動控制已由mtd做好了,不需要關心
另附一些Nand Flash知識
2440NandFlash控制器
管腳配置
D[7:0]: DATA0-7 數據/命令/地址/的輸入/輸出口(與數據總線共享)
CLE : GPA17 命令鎖存使能 (輸出)
ALE : GPA18 地址鎖存使能(輸出)
nFCE : GPA22 NAND Flash 片選使能(輸出)
nFRE : GPA20 NAND Flash 讀使能 (輸出)
nFWE : GPA19 NAND Flash 寫使能 (輸出)
R/nB : GPA21 NAND Flash 准備好/繁忙(輸入)
相關寄存器:
NFCONF NandFlash控制寄存器
[15]NandFlash控制器使能/禁止 0 = 禁止 1 = 使能
[14:13]保留
[12]初始化ECC解碼器/編碼器 0 = 不初始化 1 = 初始化
[11]芯片使能 nFCE控制 0 = 使能 1 = 禁止
[10:8]TACLS 持續時間 = HCLK*(TACLS+1)
[6:4] TWRPH0
[2:0] TWRPH1
NFCMD 命令設置寄存器
[7:0] 命令值
NFADDR 地址設置寄存器
[7:0] 存儲器地址
NFDATA 數據寄存器
[7:0] 存放數據
NFSTAT 狀態寄存器
[0] 0 = 存儲器忙 1 = 存儲器准備好
NFECC ECC寄存器
[23:16]ECC校驗碼2
[15:8] ECC校驗碼1
[8:0] ECC校驗碼0
寫操作:
寫入操作以頁為單位。寫入必須在擦除之後,否則寫入將出錯。
頁寫入周期中包括以下步驟:
寫入串行數據輸入指令(80h)。然後寫入4個字節的地址,最後串行寫入數據(528Byte)。串行寫入的數據最多為528byte。
串行數據寫入完成後,需要寫入“頁寫入確認”指令10h,這條指令將初始化器件內部寫入操作。
10h寫入之後,nand flash的內部寫控制器將自動執行內部寫入和校驗中必要的算法和時序,
系統可以通過檢測R/B的輸出,或讀狀態寄存器的狀態位(I/O 6)來判斷內部寫入是否結束
擦除操作:
擦除操作時以塊(16K Byte)為單位進行的
擦除的啟動指令為60h,隨後的3個時鐘周期是塊地址。其中只有A14到A25是有效的,而A9到A13是可以忽略的。
塊地址之後是擦除確認指令D0h,用來開始內部的擦除操作。
器件檢測到擦除確認命令後,在/WE的上升沿啟動內部寫控制器,開始執行擦除和擦除校驗。內部擦除操作完成後,應該檢測寫狀態位(I/O 0),從而了解擦除操作是否成功完成。
讀操作有兩種讀模式:
讀方式1用於讀正常數據;
讀方式2用於讀附加數據
在初始上電時,器件進入缺省的“讀方式1模式”。在這一模式下,頁讀取操作通過將00h指令寫入指令寄存器,接著寫入3個地址(一個列地址和2個行地址)來啟動。一旦頁讀指令被器件鎖存,下面的頁操作就不需要再重復寫入指令了。
寫入指令和地址後,處理器可以通過對信號線R//B的分析來判斷該才作是否完成。
外部控制器可以再以50ns為周期的連續/RE脈沖信號的控制下,從I/O口依次讀出數據
備用區域的從512到527地址的數據,可以通過讀方式2指令進行指令進行讀取(命令為50h)。地址A0~A3設置了備用區域的起始地址,A4~A7被忽略掉
時序要求:
寫地址、數據、命令時,nCE、nWE信號必須為低電平,它們在nWE信號的上升沿被鎖存。命令鎖存使能信號CLE和地址鎖存信號ALE用來區分I/O引腳上傳輸的是命令還是地址。
尋址方式:
NAND Flash的尋址方式和NAND Flash的memory組織方式緊密相關。NAND Flash的數據以bit的方式保存在memory cell,一個cell中只能存儲一個bit。這些cell以8個或者16個為單位,連成bit line,形成byte(x8)/word(x16),這就是NAND的數據寬度。
這些Line會再組成Page,典型情況下:通常是528Byte/page或者264Word/page。然後,每32個page形成一個Block,Sizeof(block)=16.5kByte。其中528Byte = 512Byte+16Byte,前512Byte為數據區,後16Byte存放數據校驗碼等,因此習慣上人們稱1page有512個字節,每個Block有16Kbytes;
現在在一些大容量的FLASH存貯設備中也采用以下配置:2112 Byte /page 或 1056 Word/page;64page/Block;Sizeof(block) = 132kByte;同上:2112 = 2048 +64,人們習慣稱一頁含2k個字節,一個Block含有64個頁,容量為128KB;
Block是NAND Flash中最大的操作單元,擦除可以按照block或page為單位完成,而編程/讀取是按照page為單位完成的
。
所以,按照這樣的組織方式可以形成所謂的三類地址:
-Block Address 塊地址
-Page Address 頁地址
-Column Address 列地址
首先,必須清楚一點,對於NAND Flash來講,地址和命令只能在I/O[7:0]上傳遞,數據寬度可以是8位或者16位,但是,對於x16的NAND Device,I/O[15:8]只用於傳遞數據。
清楚了這一點,我們就可以開始分析NAND Flash的尋址方式了。
以528Byte/page 總容量64M Byte+512kbyte的NAND器件為例:
因為
1page=528byte=512byte(Main Area)+16byte(Spare Area)
1block=32page = 16kbyte
64Mbyte = 4096 Block
用戶數據保存在main area中。
512byte需要9bit來表示,對於528byte系列的NAND,這512byte被分成1st half和2nd half,各自的訪問由所謂的pointer operation命令來選擇,也就是選擇了bit8的高低。因此A8就是halfpage pointer,A[7:0]就是所謂的column address。
32個page需要5bit來表示,占用A[13:9],即該page在塊內的相對地址。
Block的地址是由A14以上的bit來表示,例如64MB的NAND,共4096block,因此,需要12個bit來表示,即A[25:14],如果是1Gbit的528byte/page的NAND Flash,共8192個block,則block address用A[30:14]表示。
NAND Flash的地址表示為:
Block Address | Page Address in block | half page pointer | Column Address
地址傳送順序是Column Address , Page Address , Block Address。
例如一個地址:0x00aa55aa
0000 0000 1010 1010 0101 0101 1010 1010
由於地址只能在I/O[7:0]上傳遞,因此,必須采用移位的方式進行。
例如,對於64MBx8的NAND flash,地址范圍是0~0x3FF_FFFF,只要是這個范圍內的數值表示的地址都是有效的。
以NAND_ADDR為例:
第1步是傳遞column address,就是NAND_ADDR[7:0],不需移位即可傳遞到I/O[7:0]上, 而halfpage pointer即bit8是由操作指令決定的,即指令決定在哪個halfpage上進行讀寫,而真正的bit8的值是don't care的。
第2步就是將NAND_ADDR右移9位,將NAND_ADDR[16:9]傳到I/O[7:0]上;
第3步將NAND_ADDR[24:17]放到I/O上;
第4步需要將NAND_ADDR[25]放到I/O上;
因此,整個地址傳遞過程需要4步才能完成,即4-step addressing。
如果NAND Flash的容量是32MB以下,那麼,block adress最高位只到bit24,因此尋址只需要3步。