1. 基本概念

NOR FLASH是很常见的一种存储芯片,数据掉电不会丢失。NOR FLASH支持Execute On Chip,即程序可以直接在FLASH片内执行(这意味着存储在NOR FLASH上的程序不需要复制到RAM就可以直接运行)。这点和NAND FLASH不一样。因此,在嵌入式系统中,NOR FLASH很适合作为启动程序的存储介质。NOR FLASH的读取和RAM很类似(只要能够提供数据的地址,数据总线就能够正确的给出数据),但不可以直接进行写操作。对NOR FLASH的写操作需要遵循特定的命令序列,最终由芯片内部的控制单元完成写操作。
    FLASH一般都分为很多个SECTOR,每个SECTOR包括一定数量的存储单元。对有些大容量的FLASH,还分为不同的BANK,每个BANK包括一定数目的SECTORFLASH的擦除操作一般都是以SECTORBANK或是整片FLASH为单位的。在对FLASH进行写操作的时候,每个BIT可以通过编程由1变为0,但不可以有0修改为1。为了保证写操作的正确性,在执行写操作前,都要执行擦除操作。擦除操作会把FLASH的一个SECTOR,一个BANK或是整片FLASH的值全修改为0xFF。这样,写操作就可以正确完成了。
    NOR FLASH在价格上比NAND FLASH贵,且容量很小,擦除和写数据都慢,好处在于接口简单,稳定,无位反转,坏块,常用于保存关键数据,而NAND FLASH常用于保存大容量数据。

2. 硬件分析

本人使用的是韦东山老师的JZ2440开发板,CPUS3C2440A,上面所使用的NOR FLASH芯片为MX29LV160DB。连接原理图如下:
   
    从原理图中我们能看到NOR FLASH有地址线,有数据线,这些地址线数据线同样还连接到了开发板上的其他芯片(如SDRAM、DM9000、NAND FLASH),当想要去读写NOR FLASH或者操作其他芯片时都在这些I/O引脚上发出信号,我们如何让他们之间不会互相干扰,正确控制自己想要控制的芯片呢?
    S3C2440A有一个存储控制器,如下图:
   
    接在地址线或数据线上的芯片可能有很多,不同的芯片都会有CE片选引脚,可以手动控制该引脚来选择操作的芯片。              
    S3C2440A帮我们省去了这一操作,S3C2440A通过将可寻址地址(物理地址)划分为8个内存块(每个内存块128MB,8*128MB=(2^27)B,所以S3C2440A地址线共有27根(LADDR0~26)),每个内存块都有一个片选信号(nGCS0~nGCS7),具体每一部分物理地址范围如上图。以第一个部分物理地址0x00000000-0x08000000为例,当CPU读写该范围内的物理地址时,CPU会自动将nGCS0引脚拉低,挂在该片选信号线上的芯片就会被选中,本节所要讲的NOR FLASH芯片MX29LV160DBCE片选引脚就是连接到nGCS0上的,所以MX29LV160DB起始物理地址为0。

2.1 地址线错位连接

    为什么Nor Flash的地址线A0是接在2440的LADDR1上? 因为NOR FLASH的数据共有16位,也就是每个地址保存了2Byte数据,而我们的2440每个地址是保存的1Byte数据,比如:
    1. 当2440访问0X00地址时,就会读取到Nor上0地址的2Byte数据,然后2440的内存控制器会根据0x00来找到低8位字节,并返回给CPU
    2. 当2440访问0x01地址时,由于2440的LADDR0线未接,所以还是访问Nor的0地址上的2Byte数据,然后内存控制器会根据0x01来找到高8位字节,并返回给CPU
    以读ID为例,MX29LV160DB芯片手册上读ID命令如下:
   
   

MX29LV160DB芯片是16位的,所以读写操作按照Word模式,设备ID为2249。即对于MX29LV160DBID操作步骤如下:
        往地址555HAAH
        往地址2AAH55H
        往地址555H90H
        读0地址得到厂家ID: C2H
        读1地址得到设备ID: 2249H
        退出读ID状态: 给任意地址写F0H
   
因为我们2440ADDR1接到NOR FLASHA0(地址左移一位),所以对于CPU的角度来说,我们实际要操作的步骤如下:
        往地址AAAHAAH    (mw.w aaa aa)                     
        往地址55455H    (mw.w 554 55)                      
        往地址AAAH90H    (mw.w aaa 90)                     
        读0地址得到厂家ID: C2H    (md.w 0 1)              
        读2地址得到设备ID: 2249H    (md.w 2 1)     
        退出读ID状态    (mw.w 0 f0)                      
    使用上面命令在uboot下测试:
   
    可以看到正确读出来了厂家ID(0xc2)、设备ID(0x2249)

2.2 nor flash硬件开关

JZ2440中可以通过硬件开关来设置OM0Nand启动(不使用NOR FLASH)还是Nor启动,如下图所示:
      
 
    OM1在开发板上默认接地,当SW2开关接通时,OM0为1时,表示16位nor启动模式,OM0为0时,表示Nand Flash启动模式。

2.3 CFI接口

    CFI(Common Flash Interface),JEDEC(Joint Electron Device Engineering Council,电子器件工程联合委员会)制定的一个接口,用来帮助程序读取Flash的制造商ID和设备ID,确定Flash的大小,获得Flash的各个物理特性,比如block块的擦除时间等等。
    在应用CFI之前,Flash器件的有关信息都储存在系统软件的表格中。当有新的器件发布时,一般必须修改软件来添加该器件的描述信息。CFI出现后,工程师们正在利用CFI来构建代码,它不仅能够运行在现在的Flash存储器上,而且随时准备着应用在下一代的低成本版本上。这使得原始设备制造商能够在低成本Flash存储器设备可用时使用它而不必重写代码。
    CFI的好处:它可以使系统软件查询已安装的Flash Memory器件的各种参数,包括器件阵列结构参数、电气和时间参数以及器件支持的功能等。利用CFI可以不用修改系统软件就可以用新型的和改进的产品代替旧版本的产品。例如:如果新型的Flash Memory的擦除时间只有旧版本的一半,系统软件只要通过CFI读取新器件的擦除时间等参数,修改一下定时器的时间参数即可。
    MX29LV160DB芯片手册第26页中有如下所示:
  
    对于MX29LV160DB需要往地址55h写入98h,便可以进入CFI模式了,通过发送相应的地址就能得到存储在芯片内的信息,想要退出CFI模式可以发送重置命令(F0)即可返回进入CFI模式之前的模式。
   MX29LV160DB芯片手册第26~28页列出了许多读地址数据的列表,以下两个列表为例:
 
 

MX29LV160DB芯片是16位的,所以读写操作按照Word模式。即对于MX29LV160DB操作步骤如下:
        写98H到地址55H    (进入CFI模式)
        读地址10H
        读地址11H
        读地址12H
       
读地址27H    (得到容量)
   
因为我们2440ADDR1接到NOR FLASHA0(地址左移一位),所以对于CPU的角度来说,我们实际要操作的步骤如下:
       98H到地址AAH    (进入CFI模式:mw.w aa 98)
        读地址20H    (md.w 20 1)
        读地址22H    (md.w 22 1)
        读地址24H    (md.w 24 1)      
        读地址4EH    (得到容量:md.w 4e 1) 
    使用上面命令在uboot下测试:
   
    可以看到分别读MX29LV160DB10H、11H、12H可以得到“0051”、“0052”、“0059”(16进制),分别对应于ASCII“Q”、“R”、“Y”,读到的“0015”转换为10进制为21,即2^21=2MB,与芯片手册描述的完全相同,接下来看内核自带的驱动是如何操作NOR Flash的。

3. 分析内核

NOR Flash驱动也是放在内核的mtd设备中,mtd设备也知道对nor如何来读写擦除,只是不知道NOR Flash的位宽(数据线个数),基地址等,所以我们的NOR Flash驱动同样要实现硬件相关的操作,供给mtd设备调用。
    内核(linux-2.6.22.6)自带的NOR Flash驱动位于drivers/mtd/maps/physmap.c,首先进入该驱动入口函数。

3.1 physmap_init()入口函数

physmap_init()入口函数代码如下:

/*
static struct platform_driver physmap_flash_driver = {.probe       = physmap_flash_probe,.remove      = physmap_flash_remove,
#ifdef CONFIG_PM.suspend    = physmap_flash_suspend,.resume        = physmap_flash_resume,.shutdown   = physmap_flash_shutdown,
#endif.driver       = {.name   = "physmap-flash",},
};
*/static int __init physmap_init(void)
{int err;err = platform_driver_register(&physmap_flash_driver);
#ifdef PHYSMAP_COMPATif (err == 0)platform_device_register(&physmap_flash);
#endifreturn err;
}

使用platform设备驱动模型(参考十一、Linux驱动之platform总线设备驱动)构造并注册platform_driver结构体,并且注册了platform_device结构体,定位到传入的platform_device结构体physmap_flash相关内容如下:

/*CONFIG_MTD_PHYSMAP_BANKWIDTH 表示位宽*/
static struct physmap_flash_data physmap_flash_data = {.width      = CONFIG_MTD_PHYSMAP_BANKWIDTH,
};/**CONFIG_MTD_PHYSMAP_START 表示物理基地址*CONFIG_MTD_PHYSMAP_LEN  表示长度*/
static struct resource physmap_flash_resource = {.start        = CONFIG_MTD_PHYSMAP_START,.end        = CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1,.flags        = IORESOURCE_MEM,
};static struct platform_device physmap_flash = {.name     = "physmap-flash",.id        = 0,.dev       = {.platform_data  = &physmap_flash_data,},.num_resources = 1,.resource  = &physmap_flash_resource,
};

可以看出,在physmap_flash结构里传入了设备资源,例如NOR Flash芯片的物理基地址,位宽,长度。可以通过通过对内核menuconfig菜单配置,也可以直接赋值。配置内核体验一下,ubuntu进入内核目录,执行如下:
      cd /work/system/linux-2.6.22.6
      make munuconfig    (配置内核步骤如下)

        Device Drivers  --->
              Memory Technology Device (MTD) support  --->
                    Mapping drivers for chip access  --->
                          <M> CFI Flash device in physical memory map
                                (0x0) Physical start address of flash mapping   (物理基地址)
                                (0x1000000) Physical length of flash mapping   (长度,要>=norflash真实长度)
                                (2)   Bank width in octets (NEW)    (位宽,2字节)
      make uImage
      make modules   
      cp arch/arm/boot/uImage /work/nfs_root/first_fs    (拷贝新内核到网络文件系统上)
      cp drivers/mtd/maps/physmap.ko /work/nfs_root/first_fs    (拷贝该驱动到网络文件系统上)
   
uboot命令行状态下载新内核启动:
      nfs 0x30000000 192.168.1.13:/work/nfs_root/first_fs/uImage
      bootm 30000000
   
装载驱动与查看norflash信息,进入该驱动文件目录,执行如下命令:
       insmod physmap.ko    (可以看到设置的参数被打印出来了)
     

       ls dev/mtd*
     

    可以看到创建了2个mtd0字符设备,一个mtd0块设备
   
回到入口函数,当设备与驱动匹配成功便会调用platform_driver->probe成员函数,即physmap_flash_probe()函数。

3.2 physmap_flash_probe()函数

    physmap_flash_probe()函数部分代码如下:

/*
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;... .../*分配结构体*/info = kzalloc(sizeof(struct physmap_flash_info), GFP_KERNEL);/*设置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;/*设置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;        /*添加mtd设备*/add_mtd_device(info->mtd);              return 0;err_out:physmap_flash_remove(dev);  //该函数用来注销之前注册的东西return err;
}

3.2.1 probe函数主要结构

可以看出类似上一节十七、Linux驱动之nand flash驱动,该驱动主要有以下几点:
      1. 分配mtd_info结构体和map_info结构体
      2. 设置map_info结构体
      3. 设置mtd_info结构体
      4. 使用add_mtd_partitions()或者add_mtd_device()来创建MTD字符/块设备
   
其中通过do_map_probe()函数来识别NOR Flash获取硬件信息(CFI模式与jedec模式)。

3.2.2 do_map_probe()函数

    该函数通过使用CFI模式或jedec模式来识别NOR Flash(读取Flash的制造商ID和设备ID,确定Flash的大小,获得Flash的各个物理特性,比如block块的擦除时间等等
CFI模式识别过程如下:
    do_map_probe("cfi_probe", s3c_nor_map);
        drv = get_mtd_chip_driver(name)    //得到对应名字的驱动函数
        ret = drv->probe(map);    // 调用到cfi_probe.c文件里的cfi_probe函数
            cfi_probe
                mtd_do_chip_probe
                    cfi = genprobe_ident_chips
                        genprobe_new_chip
                            cp->probe_chip
                                 cfi_probe_chip
                                     cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL);    //进入CFI模式
                                                qry_present(map,base,cfi)    // 看是否能读出"QRY"
                                                ... ...
    使用CFI模式读取Flash芯片上所有信息。

jedec模式识别过程如下:
    do_map_probe("jedec_probe", s3c_nor_map);
        drv = get_mtd_chip_driver(name)    //得到对应名字的驱动函数
        ret = drv->probe(map);    // 调用到jedec_probe .c文件里的cfi_probe函数
              jedec_probe
                mtd_do_chip_probe(map, &jedec_chip_probe);
                    genprobe_ident_chips(map, cp);
                        genprobe_new_chip(map, cp, &cfi)
                            cp->probe_chip(map, 0, NULL, cfi)
                                    jedec_probe_chip
                                        /*进入读ID*/
                                        cfi_send_gen_cmd(0xaa, ...)
                                        cfi_send_gen_cmd(0x55, ...)
                                        cfi_send_gen_cmd(0x90, ...)
                                        /*得到厂家ID,设备ID*/
                                         cfi->mfr = jedec_read_mfr(map, base, cfi);
                                         cfi->id = jedec_read_id(map, base, cfi);
                                         jedec_table     //通过ID来匹配jedec_table[]数组
    两者区别:
CFI模式是读取Flash芯片上所有信息。jedec模式是读取Flash芯片上ID信息,如果与内核里保存各种NOR Flash信息的数组某一项的ID匹配,使用该数组里的信息。

4. 编写代码

驱动程序s3c_nor.c代码如下:

/** 参考 drivers\mtd\maps\physmap.c*/
#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 map_info *s3c_nor_map;
static struct mtd_info *s3c_nor_mtd;static struct mtd_partition s3c_nor_parts[] = {[0] = {.name   = "bootloader_nor",.size   = 0x00040000,.offset = 0,},[1] = {.name   = "root_nor",.offset = MTDPART_OFS_APPEND,.size   = MTDPART_SIZ_FULL,}
};static int s3c_nor_init(void)
{/* 1. 分配map_info结构体 */s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);/* 2. 设置: 物理基地址(phys), 大小(size), 位宽(bankwidth), 虚拟基地址(virt) */s3c_nor_map->name = "s3c_nor";s3c_nor_map->phys = 0;s3c_nor_map->size = 0x1000000; /* >= NOR的真正大小 */s3c_nor_map->bankwidth = 2;s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size);simple_map_init(s3c_nor_map);/* 3. 使用: 调用NOR FLASH协议层提供的函数来识别 */printk("use cfi_probe\n");s3c_nor_mtd = do_map_probe("cfi_probe", s3c_nor_map);  //cfi模式识别芯片if (!s3c_nor_mtd){printk("use jedec_probe\n");s3c_nor_mtd = do_map_probe("jedec_probe", s3c_nor_map);    //jedec模式识别芯片}if (!s3c_nor_mtd){       iounmap(s3c_nor_map->virt);kfree(s3c_nor_map);return -EIO;}/*4. 使用add_mtd_partitions()或者add_mtd_device()来创建MTD字符/块设备*/add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, 2);return 0;
}static void s3c_nor_exit(void)
{del_mtd_partitions(s3c_nor_mtd);iounmap(s3c_nor_map->virt);kfree(s3c_nor_map);
}module_init(s3c_nor_init);
module_exit(s3c_nor_exit);
MODULE_LICENSE("GPL");

    Makefile代码如下:

KERN_DIR = /work/system/linux-2.6.22.6  //内核目录all:make -C $(KERN_DIR) M=`pwd` modules clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-m    += s3c_nor.o

5. 测试

内核:linux-2.6.22.6
编译器:arm-linux-gcc-3.4.5
环境:ubuntu9.10

5.1 配置内核并开发板启动

使用3.1方式配置内核并下载到开发板上启动(一定要在nor启动下挂载才行,因为2440使用nand启动时,是访问不了nor的前4k地址)

5.2 测试

1. 安装驱动。在开发板驱动文件目录下执行命令:
      insmod s3c_nor.ko
     
      ls /dev/mtd* -l     (新出现的设备就是norflash设备)
     
    2. 格式化。执行如下命令:
      flash_eraseall -j dev/mtd1    (格式化:加上-j格式化为jffs2,用字符设备格式)
 
   3. 挂载
      mount -t jffs2 dev/mtdblock1 /mnt/    (挂接:用块设备格式将分区2挂接到/mnt下,卸载:umount /mnt,不能在/mnt目录下卸载)
   
这样就能直接在/mnt目录下读写文件,实际是操作到NOR Flashmtdblock1块设备中!

十八、Linux驱动之nor flash驱动相关推荐

  1. linux用户空间flash驱动,全面掌握Linux驱动框架——字符设备驱动、I2C驱动、总线设备驱动、NAND FLASH驱动...

    原标题:全面掌握Linux驱动框架--字符设备驱动.I2C驱动.总线设备驱动.NAND FLASH驱动 字符设备驱动 哈~ 这几天都在发图,通过这种方式,我们希望能帮大家梳理学过的知识,全局的掌握Li ...

  2. Linux驱动开发(十八)---网络(网卡)驱动学习

    前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...

  3. 《Linux驱动:Nor flash驱动看这一篇就够了》

    文章目录 一,前言 二,硬件电路 2.1 脚位功能 2.2 地址移位 三,Nand Flash和Nor Flash的区别 四,Nor flash CFI规范和JEDEC规范 4.1 JEDEC规范 4 ...

  4. 《Linux驱动:nand flash驱动看这一篇就够了》

    文章目录 一,前言 二,硬件电路 2.1 Nand flash相关 2.2 S3c2440相关 2.3 Nand flash 位反转 三,Nand flash驱动框架 四,S3c2440 Nand F ...

  5. 块设备驱动之NOR FLASH驱动

    转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/25240947 一.硬件原理 从原理图中我们能看到NOR FLASH有地址线,有 ...

  6. 【海思篇】【Hi3516DV300】十八、TSENSOR 芯片温度检测驱动 开发

    文章目录 一 芯片操作步骤 二 驱动层实现 三 应用层实现 四 测试 一 芯片操作步骤 参考<<Hi3516DV300专业型Smart IP Camera SoC用户指南.pdf>& ...

  7. RHEL6基础之十八Linux中Kill进程的方法

    Linux中的kill命令用来终止指定的进程(terminate a process)的运行,是Linux下进程管理的常用命令.工作原理是向Linux系统的内核发送一个系统操作信号和某个程序的进程标识 ...

  8. nand flash 经典 全面 ------如何编写Linux下Nand Flash驱动

    Crifan Li 摘要 本文先解释了Nand Flash相关的一些名词,再从Flash硬件机制开始,介绍到Nand Flash的常见的物理特性,且深入介绍了Nand Flash的一些高级功能,然后开 ...

  9. 如何编写linux下nand flash驱动

    http://www.cnblogs.com/sankye/articles/1638852.html 向作者Sankye致敬 [编写驱动之前要了解的知识] 1.       硬件特性: [Flash ...

最新文章

  1. 到底什么是故事点(Story Point)?
  2. 【逆向】UE4 渲染流程分析
  3. Python应用matplotlib绘图简介
  4. 云服务器 ECS 建站教程:手工部署Java Web项目
  5. sqlserver空间数据 + c# 实现查询附近的设备
  6. java性能最好的mvc框架_详解Spring MVC的异步模式(高性能的关键)
  7. 浏览器同源政策及其规避方法
  8. 【渝粤教育】国家开放大学2018年秋季 0676-22T物流成本管理 参考试题
  9. Java入门第65课——根据周长计算不同形状图形的面积
  10. 中国石油大学《微观经济学》第一次在线作业
  11. 几大原型开发软件对比[转]
  12. sqlmap之sql注入(一)
  13. 火车票抢票API 根据乘客的车次与座席要求快速订票出票 1
  14. 台湾安格推出的用于TYPEC转VGA HDMI音视频多功能扩展坞方案选型和方案设计|USB-C转VGA HDMI类扩展器方案选型和方案讲解
  15. win10局域网下利用 FlieZilla 搭建FTP服务器,实现手机平板用nplayer直接看电脑内的视频
  16. 3D打印入门必读工具书《解析3D打印机》免费下载!
  17. 制作价目表的程序和软件
  18. 美女图片整站源码 wordpress主题多功能CX-UDY图片主题下载 带会员积分系统
  19. WebKit(WKScriptMessageHandler)
  20. np.mean()和np.std()函数

热门文章

  1. 图像增强三大类别:点增强、空域增强、频域增强
  2. 笔记本外接显示器掉帧排bug过程
  3. JAVA微信扫码支付及微信App支付开发(模式二)完整功能实现
  4. Day06-Esayexcel简介及写、读操作-p95、96
  5. 计算机网络谢希仁第七版知识点总结
  6. 莫烦python pytorch_[莫烦 PyTorch 系列教程] 1.2 – 安装 PyTorch
  7. 生成16位卡号和激活码
  8. espwho-esp32cam-vscode开发使用
  9. 【BZOJ4545】DQS的trie
  10. Python的数学建模课-02.数据导入