1.nor硬件介紹:

從原理圖中我們能看到NOR FLASH有地址線,有數據線,它和我們的SDRAM接口相似,能直接讀取數據,但是不能像SDRAM直接寫入數據,需要有命令才行
1.1其中我們2440的地址線共有27根(LADDR0~26),為什么是27根?
因為2440共有7個bank內存塊,每個bank=128MB=(2^27)B,所以共有27根數據線
1.2為什么Nor Flash的地址線A0是接在2440的LADDR1上?
因為Nor Flash的數據共有16位,也就是每個地址保存了2B數據,而我們的2440每個地址是保存的1B數據,
比如:
當2440訪問0X00地址時,就會讀取到Nor上0地址的2B數據,然后2440的內存控制器會根據0x00來找到低8位字節,並返回給CPU,
當2440訪問0x01地址時,由於2440的LADDR0線未接,所以還是訪問Nor的0地址上的2B數據,然后內存控制器會根據0x01來找到高8位字節,並返回給CPU
1.3 nand和nor區別:
nor flash在價格上比nand貴,且容量很小 ,擦除和寫數據都慢,好處在於接口簡單,穩定,無位反轉,壞塊,常用於保存關鍵數據,而nand flash常用於保存大容量數據
在2440中是通過硬件開關來設置OM0為Nand啟動還是Nor啟動,如下圖所示:

OM0具體參數如下所示,其中2440的OM1引腳默認接地

對於nand啟動:OM0接地,nand flash的開始4KB會自動地被加載到2440內置的SRAM緩存器中,就可以直接讀寫
對於nor啟動:OM0接高,2440訪問的內存就是nor flash,可以直接讀,但是不能直接寫
2.nor flash命令如下所示(參考MX29LV160DBTI.pdf)

其中word是針對16位nand,byte針對8位nand.
由於我們2440的flash型號是MX29LV160DB,所以設備ID為0x2249
2.1 比如,當我們要program(往0x20地址寫入0xff數據)時
需要以下3步:
1.發送解鎖地址:
往nor地址0x555寫入0xAA
往nor地址0x2AA寫入0x55
2.發送命令:
往nor地址0x555寫入0xA0 //進入program模式
3.寫數據:
往nor地址0x20(PA)寫入0xff(PD) //往0x20寫入0xff
(接下來就會一直是program模式,執行reset模式便可以退出)
2.2該NOR有兩種規范, jedec, cfi(common flash interface)
jedec
就是和nandflash的一樣,通過讀ID來匹配linux內核中drivers/mtd/chips/jedec_probe.c里的jedec_table[]數組,來確定norflash的各個參數(名稱、容量、位寬等),如下圖所示:

- [0] = MTD_UADDR_0x5555_0x2AAA
表示解鎖地址為0x5555,0x2AAAM,其中數組[0],表示屬於8位flash,定義如下:
![]()
- CmdSet
使用哪種命令,一般CmdSet=0xFFF0
- .NumEraseRegions= 1
只有1個不同的扇區區域
- ERASEINFO(0x10000, 64)
共有64個扇區,每個扇區都是64KB(0x10000)
cfi
就是將這些參數保存在cfi模式下指定地址中, 往nor的0x55地址寫入0x98,即可進入cfi模式,
cfi模式部分命令如下圖所示:

當我們在cfi模式下,比如:讀取nor地址0x27處的數據,便能讀到nor的容量
如下圖所示,之所以地址*2,是因為nor地址線A0接在我們2440的A1上(退出cfi模式,使用復位命令即可)

讀到0X15,0x15=21,如下圖,剛好對應我們原理圖的21根nor地址線,所以容量為2^21=2MB

2.3為什么上圖的A20引腳沒有接?
對於2440來講,因為此時的A0~A19的容量剛好為2MB,與cfi模式下讀取的數據一致,所以沒有接A20
3.接下來便來分析如何寫norflash驅動
3.1 先來回憶下之前的nandflsh驅動:
nandflsh驅動會放在內核的mtd設備中,而mtd設備知道如何通過命令/地址/數據來操作nandflash,所以我們之前的nandflash驅動只實現了硬件相關的操作(構造mtd_info,nand_chip結構體、啟動nand控制器等)
同樣地,norflash驅動也是放在內核的mtd設備中,mtd設備也知道對nor如何來讀寫擦除,只是不知道norflash的位寬(數據線個數),基地址等,所以我們的norflash驅動同樣要實現硬件相關的操作,供給mtd設備調用
3.2參考內核自帶的nor驅動:drivers/mtd/maps/physmap.c
進入它的init函數:

發現注冊了兩個platform平台設備驅動,進入physmap_flash結構體中:

發現3個未定義的變量:
CONFIG_MTD_PHYSMAP_BANKWIDTH: nandflash的字節位寬
CONFIG_MTD_PHYSMAP_START:nandflash的物理基地址
CONFIG_MTD_PHYSMAP_LEN: nandflash的容量長度
這3個變量是通過linux的menuconfig菜單配置出來的,若自己填入值,就不需要用menuconfig菜單配置了
3.3接下來我們就來配置內核,然后掛載這個內核自帶的norflash驅動實驗一番
3.4 首先make menuconfig,配置上面3個變量,然后設為模塊
-> Device Drivers
-> Memory Technology Device (MTD) support
-> Mapping drivers for chip access //進入映射驅動
<M> CFI Flash device in physical memory map //將支持cfi的norflash設置為模塊
- (0x0) Physical start address of flash mapping // 設置物理基地址
- (0x1000000) Physical length of flash mapping // 設置容量長度,必須大於等於自身nor的2MB
- (2) Bank width in octets (NEW) // 設置字節位寬,因為nor為16位,所以等於2
3.5 make modules 編譯模塊
如下圖所示,可以看到physmap.c編譯成.ko模塊了

3.6 然后放在nfs目錄下,啟動開發板
如下圖所示,insmod后打印了一串信息:

如下圖所示,可以看到創建了2個mtd0字符設備,一個mtd0塊設備:

4.接下來我們便分析physmap.c,如何寫出norflash驅動的
其中physmap.c的probe函數如下
struct physmap_flash_info { struct mtd_info *mtd; //實現對flash的讀寫擦除等操作 struct map_info map; //存放硬件相關的結構體 struct resource *res; #ifdef CONFIG_MTD_PARTITIONS int nr_parts; struct mtd_partition *parts; #endif }; static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", "map_rom", NULL }; //芯片名稱
... ... static int physmap_flash_probe(struct platform_device *dev) { const char **probe_type; ... ... /*1. 分配結構體*/ info = kzalloc(sizeof(struct physmap_flash_info), GFP_KERNEL); /*2.設置map_info 結構體*/ info->map.name = dev->dev.bus_id; //norflash的名字 info->map.phys = dev->resource->start; //物理基地址 info->map.size = dev->resource->end - dev->resource->start + 1; //容量長度 info->map.bankwidth = physmap_data->width; //字節位寬 info->map.virt = ioremap(info->map.phys, info->map.size); //虛擬地址
simple_map_init(&info->map); //簡單初始化map_info的其它成員 probe_type = rom_probe_types; /*3. 設置mtd_info 結構體 */ /*通過probe_type指向的名稱來識別芯片,當do_map_probe()函數返回NULL表示沒找到*/ /*當找到對應的芯片mtd_info結構體,便返回給當前的info->mtd */ for (; info->mtd == NULL && *probe_type != NULL; probe_type++) info->mtd = do_map_probe(*probe_type, &info->map); //通過do_map_probe ()來識別芯片 if (info->mtd == NULL) { //最終還是沒找到芯片,便注銷之前注冊的東西並退出 dev_err(&dev->dev, "map_probe failed\n"); err = -ENXIO; goto err_out; } info->mtd->owner = THIS_MODULE; /*4.添加mtd設備*/ add_mtd_device(info->mtd); return 0; err_out: physmap_flash_remove(dev); //該函數用來注銷之前注冊的東西 return err; }
通過上面的代碼和注釋分析到,和我們上一節的nandflash驅動相似,這里是設置map_info 結構體和mtd_info結構體來完成的,當我們要對norflash分區就要使用add_mtd_partitions()才行
其中當*probe_type==“cfi_probe”時:
就會通過do_map_probe("cfi_probe", &info->map)來識別芯片.
最終會進入drivers/mtd/chips/cfi_probe.c中的cfi_probe_chip()函數來進入cfi模式,讀取芯片信息
當*probe_type=="jedec_probe"時:
最終會進入drivers/mtd/chips/jedec_probe.c中的jedec_probe_chip ()函數來使用讀ID命令,通過ID來匹配jedec_table[]數組.
所以注冊一個塊設備驅動,需要以下步驟:
- 1. 分配mtd_info結構體和map_info結構體
- 2. 設置map_info 結構體
- 3. 設置mtd_info 結構體
- 4. 使用add_mtd_partitions()或者add_mtd_device()來創建MTD字符/塊 設備
5.接下來我們來參考physmap.c來自己寫norflah驅動
代碼如下:
#include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/device.h> #include <linux/platform_device.h> #include <linux/mtd/mtd.h> #include <linux/mtd/map.h> #include <linux/mtd/partitions.h> #include <asm/io.h> static struct mtd_info *mynor_mtd_info; static struct map_info *mynor_map_info; static struct mtd_partition mynor_partitions[] = { [0] = { .name = "bootloader", .size = 0x00040000, .offset = 0, }, [1] = { .name = "root", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, } }; static const char *mynor_probe_types[] = { "cfi_probe", "jedec_probe",NULL}; static int mynor_init(void) { int val; /*1. 分配map_info 結構體和mtd_info結構體*/ mynor_mtd_info=kzalloc(sizeof(struct mtd_info), GFP_KERNEL); mynor_map_info=kzalloc(sizeof(struct map_info), GFP_KERNEL); /*2. 設置map_info 結構體*/ mynor_map_info->name="my_nor"; mynor_map_info->phys=0x0; //物理地址 mynor_map_info->size=0x1000000; //=16M,長度必須大於等於norflash的2M容量 mynor_map_info->bankwidth=2; //16位寬 mynor_map_info->virt = ioremap(0x0, mynor_map_info->size); //虛擬地址 simple_map_init(mynor_map_info); /*3. 設置mtd_info 結構體*/
mynor_mtd_info = do_map_probe("cfi_probe", mynor_map_info);
if (!mynor_mtd_info)
{
mynor_mtd_info = do_map_probe("jedec_probe", mynor_map_info);
}
if (!mynor_mtd_info)
{
printk("not available norflash !!!\r\n");
goto err_out;
}
mynor_mtd_info->owner=THIS_MODULE;
/*4. 使用add_mtd_partitions()或者add_mtd_device()來創建MTD字符/塊 設備*/ add_mtd_partitions(mynor_mtd_info,mynor_partitions,2); return 0; err_out: iounmap(mynor_map_info->virt); //取消虛擬地址映射 kfree(mynor_map_info); kfree(mynor_mtd_info); return 0; } static void mynor_exit(void) { del_mtd_partitions(mynor_mtd_info); //卸載分區 iounmap(mynor_map_info->virt); //取消虛擬地址映射 kfree(mynor_map_info); kfree(mynor_mtd_info); } module_init(mynor_init); module_exit(mynor_exit); MODULE_LICENSE("GPL");
6.掛載驅動試驗
(一定要在nor啟動下掛載才行,因為2440使用nand啟動時,是訪問不了nor的前4k地址)
insmod掛載驅動后,如下圖所示:

可以看到創建了兩個分區“bootloader”,“root”,如下圖所示,可以看到創建了2對mtd字符/塊設備

6.1 接下來便來對root分區(mtd1)來試驗(使用flash之前最好擦除一次)
步驟如下:
./flash_eraseall -j /dev/mtd1 //使用mtd-util工具的flash_eraseal命令來擦除root分區(mtd1) mount -t jffs2 /dev/mtdblock1 /mnt/ //使用mount掛載文件系統, -t:文件系統類型(type)
接下來就可以在/mnt目錄下來任意讀寫文件了,最終會保存在flash的mtdblock1塊設備中
(PS:可以參考內核自帶的mtdram.c,里面是使用內存來模擬flash, 里面通過memcopy()等來實現對內存讀寫擦除)
下章學習 : 26.Linux-網卡驅動介紹以及制作虛擬網卡驅動(詳解)
