驱动专题:第五章MTD及Flash驱动 4.Norflash驱动

2019-07-13 08:57发布

嵌入式Linux——nor flash (1):硬件介绍


声明:本文章是学完韦东山老师的教学视频后看过一些文章后所写,在看文章时我会将一些可能重要的知识点复制下来,所以可能会抄到您文章中的一些内容,如果您有什么意见,请同我讲出我会修改或删除。而如果这篇文章对您有帮助,那是我的荣幸。同时我看到网上有一些很好的写nor驱动的文章,但是我发现这些文章中对于nor的硬件描述以及nor与开发板的联系上并不是说的十分清楚,所以我想借这篇文章说一些关于硬件的事。    下面我们言归正传说芯片,我用的这款nor芯片为MX29LV160D,而下面是一些他的基础特征:•Byte/Word mode switchable(支持byte/Word两种模式-2,097,152 x8 / 1,048,576 x16 (2M*8bit /1M*16bit):文本使用的是1M*16bit,后面会在原理图上说明。• 支持电压Vcc2.7 to 3.6 volt for read, erase, and program operations•Support Common Flash Interface (CFI) (支持CFI•Ready/Busy# (RY/BY#) Output -Provides a hardware method of detecting program and erase operation completion (提供硬件检测编码或擦除)•Hardware Reset (RESET#) Input-Provides a hardware method to reset the internal state machine to read mode(提供硬件重启)Thememory array is divided into 32 equal 64 Kilo byte blocks分成了32块64Kbit的块Thesoftware algorithm used for this device also adheres to the JEDEC standard forsingle power supply devices.软件算法支持jedec标准而下面我们介绍硬件的引脚图,并对引脚进行说明:符号引脚名A0~A19地址引脚Q0~Q14数据引脚Q15/A-1Q15Word模式:两字节模式)/LSB 地址(Byte模式:单字节模式)CE#(片选输入使能)WE#写输入使能BYTE#Word/Byte 选择输入RESET#硬件重启引脚/扇区保护不锁定OE#输出使能RY/BY#就绪/输出VCC电源正极(2.7V~3.6V)GND电源地WP#/ACC硬件写保护/加速引脚NC不连接注意:如果用户不需要WP#/ACC(硬件写保护/加速引脚)功能,请将这个引脚接到电源正极或者让他悬空。当WP#/ACC(硬件写保护/加速引脚)悬空时,其内部有一个上拉到Vih的电阻。
而该nor芯片与S3C2440的接线图为:

看完这个接线图可能会有读者会问:为什么S3C2440的地址1引脚与nor芯片的地址0相连,地址1和地址2相连。。。。以此下去S3C2440的地址20和nor芯片的地址19相连?要回答这个问题我们就要从S3C2440的数据传输结构讲起,S3C2440是以byte(8bit)为单位来传输数据的。而我们可以看到上图中nor芯片是以16bit(2byte)的形式传递数据的,这样就造成S3C2440所传递的地址0和地址1的值对应于nor中地址0的值,而S3C2440所传递的地址2和地址3的值对应于nor中地址1的值,这样就造成S3C2440的地址<<1 = nor地址中的值。所以就造成S3C2440中地址0不去接,而直接用地址1接nor的地址0. 或者我引用我在网上看到的一个很好的解释:为什么Nor Flash的地址线A0是接在2440LADDR1?因为NorFlash的数据共有16,也就是每个地址保存了2B数据,而我们的2440每个地址是保存的1B数据,比如:2440访问0X00地址时,就会读取到Nor0地址的2B数据,然后2440的内存控制器会根据0x00来找到低8位字节,并返回给CPU,2440访问0x01地址时,由于2440LADDR0线未接,所以还是访问Nor0地址上的2B数据,然后内存控制器会根据0x01来找到高8位字节,并返回给CPU不过上面的介绍您是否看到懂,但是在S3C2440手册中对于16位ROM设备的接线他是有介绍的:
手册规定16位ROM就应该这样接。如果您实在理解不了,您只要记住就好了。而对于问题:为什么我们的地址线是从A0到A19一共有20条?    那是因为我们的芯片的大小为1M*16,而1M=2^20,所以通过20条地址线加16条数据线我们就可以访问到芯片的任何位置的数据了。
2440中是通过硬件开关来设置OM0为Nand启动还是Nor启动,如下图所示:

通过上面两幅电路图我们知道OM1是一直接地的,所以:对于nand启动:OM0接地,nand flash的开始4KB会自动地被加载到2440内置的SRAM的“steppingstone”,就可以直接读写对于nor启动:OM0接高,2440访问的内存就是nor flash,可以直接读,但是不能直接写像上面说的那样nor是一种ROM可以像内存一样直接通过地址读数据,却不能随便的写,而要想对nor进行读写就要通过特殊的命令了。下面是命令表


本芯片为MX29LV160DT:所以设备ID为: 22C4同时由于本芯片支持CFI,所以我们可以通过查询CFI获得设备的操作特征,架构,以及供应商特有的信息如:ID信息,内存大小,byte/word设置,操作电压和这个设备的时间信息。而CFI对应的命令为:



我们后面的驱动程序会讲到通过查询CFI获得的芯片信息来确定芯片的驱动。当然这都是后话了。我们讲完CFI我们就应该讲一下我们读写芯片时所涉及到的时序问题了:下面我们先讲写命令时序图
为了写一个命令到设备中,系统要将WE#和CE#设为低电平,而OE#设为高电平。在一个命令周期中,当CE#和WE#的最后一个下降沿时所有的地址值被锁存,而当CE#和WE#的第一个上升沿时所有的数据被锁存。接下来我们讲:读数据
在存储设备上电或者重启后他会自动进入读序列状态。如果微控制器想要读存储在序列中的数据,他必须将CE#(设备片选引脚)和OE#(输出控制引脚)设为低电平,同时输入想要读的数据的地址。经过一段时间的读周期后,想要被读的值就显示到输出引脚,来供微处理器读取。如果CE#或者OE#被设为高,输出的将是tri态,同时在输出引脚将没有数据展现。当存储设备完成嵌入式操作后(自动擦除或者编程),他将自动切换到读序列状态,这时,设备的任何地址的数据都可以被读取。而如果设备在擦除操作时收到了擦除悬挂的命令后,擦除操作将要暂停,但暂停时间不会超过Tready1,暂停的这段时间之后,设备将会自动回到读序列状态。这个时候,可以读存储在设备中的任意地址的数据,除了被擦除的扇区中。在擦除悬挂这段期间,如果用户想要读取存储在被擦除扇区中的数据,设备将输出状态值到输出端。相似的,在擦除悬挂期间如果发出编码命令后,编码完成后,系统同样可以读设备任何地方的值,除了被擦除的地方。在以下两种情况下可以发送重启命令以读取数据:1. 在编码或者擦除操作时,编码或者擦除错误而造成Q5为高时。2. 设备在自动选择模式或者CFI模式时。
而现在我们通过2440开发板的uboot来体验一下nor的操作,好让我们对nor有更清楚的认识(使用前开发板设为nor启动进入uboot)。而在操作前先介绍一下要用到的uboot中的命令:md(memory display)[.b(byte) .w(word) .long(4byte)]  [地址]  (次数)   mw(memory write) [.b(byte) .w(word) .long(4byte)]  [地址]   [数据]     ,其中"[]"中的内容为必填项,而“()”中的内容为选填项
1. 读数据  
md.b 0  1  //读0地址一字节, 读一次
  
2. 读ID NOR手册上:  
往地址555H写AAH  
往地址2AAH写55H  
往地址555H写90H  
读0地址得到厂家ID: C2H  
读1地址得到设备ID: 22DAH或225BH  
退出读ID状态: 给任意地址写F0H  
  
UBOOT的操作  (2440的A1接到NOR的A0,所以2440发出(555h<<1), NOR才能收到555h这个地址  ):
往地址AAAH写AAH                      mw.w aaa aa  
往地址554写55H                       mw.w 554 55  
往地址AAAH写90H                      mw.w aaa 90  
读0地址得到厂家ID: C2H               md.w 0 1  
读2地址得到设备ID: 22DAH或225BH      md.w 2 1  
退出读ID状态:                        mw.w 0 f0  
  
3. NOR有两种规范, jedec, cfi(common flash interface)  
   读取CFI信息  

  
NOR手册:     
进入CFI模式    往55H写入98H  
读数据:            读10H得到0051  
                       读11H得到0052  
                       读12H得到0059  
                       读27H得到容量  
  
UBOOT的操作
进入CFI模式    往AAH写入98H            mw.w aa 98  
读数据:            读20H得到0051           md.w 20 1  
                       读22H得到0052           md.w 22 1  
                       读24H得到0059           md.w 24 1  
                       读4EH得到容量           md.w 4e 1  
                       退出CFI模式             mw.w 0 f0  
  
4. 写数据: 在地址0x100000写入0x1234  直接写:md.w 100000 1     // 得到ffff  
mw.w 100000 1234  
md.w 100000 1     // 还是ffff  
  
NOR手册:  
往地址555H写AAH   
往地址2AAH写55H   
往地址555H写A0H   
往地址PA写PD  
  
UBOOT的操作  
往地址AAAH写AAH               mw.w aaa aa  
往地址554H写55H               mw.w 554 55  
往地址AAAH写A0H               mw.w aaa a0  
往地址0x100000写1234h         mw.w 100000 1234  

上面就是我所介绍的nor硬件的全部,而关于下节我会介绍nor中的驱动层次。

嵌入式Linux——nor flash(2):do_map_probe分析

在分析nor驱动之前,我想先分析一下do_map_probe这个函数,因为是这个函数帮助我们做了探测工作,来确定我们所用的芯片的一些重要信息。同时也只有分析清楚这个函数我们才能对下面的将设备的注册有所了解。        而在说这些之前我想先引进一张图来介绍Flash框架层次:

上面这幅图就是对flash框架层次的描述,当然图还有些欠缺,如:在nor下应该再分两个层次分别为:CFI层和jedec层,同时与MTD层平行的还有一层:用RAM模拟MTDdrivers/mtd/devices/mtdram.c)。而我们这篇文章主要讲nor,所以我们将主要的讲解放到nor的do_map_probe的分析,而这个图就是希望大家对flash设备有一个整体的认识。这样对将来对问题的分析,或者说一会儿对do_map_probe的分析都是有好处的。而讲这个函数的原因,我们在开头已经讲了。而另一个重要的原因是,通过do_map_probe的分析,我们可以对nor的层次有一个了解,了解他是怎么从软件的代码到硬件命令的实施过程。下面我们言归正传讲do_map_probe函数:我们知道nor有两种规范CFI和jedec,我们先讲CFI规范下的do_map_probe函数:(driversmtdmapsphysmap.c)probe_type ="cfi_probe"do_map_probe(*probe_type,&info->map)(driversmtdchipschipreg.c)我们查看代码发现该函数只做了两件事情:1.获得芯片的驱动:get_mtd_chip_driver(name);2.调用获得驱动的probe函数:drv = get_mtd_chip_driver(name)所以我们应该先完成第一件事情:get_mtd_chip_driver(name); /* 主要就是遍历芯片驱动链表 ,如果链表中驱动的名字与目标相同,则返回该驱动*/
    list_for_each(pos, &chip_drvs_list) {
this = list_entry(pos, typeof(*this), list);
if (!strcmp(this->name, name)) {
ret = this;
break;
}
}
因此我们现在的任务变为确定:是谁定义了chip_drvs_list?通过观察我们知道是register_mtd_chip_driver函数定义的:void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{
spin_lock(&chip_drvs_lock);
list_add(&drv->list, &chip_drvs_list);
spin_unlock(&chip_drvs_lock);
}我们知道这是一个注册mtd_chip_driver的函数,那一定有人调用他来注册mtd_chip_driver,而那个注册mtd_chip_driver就是我们在上边想要找的。那是谁调用了register_mtd_chip_driver函数?查代码知是:static int __init cfi_probe_init(void)(driversmtdchipscfi_probe.c)
{
register_mtd_chip_driver(&cfi_chipdrv);
return 0;
}而cfi_chipdrv就是我们上面第二件事中的驱动。完成了第一件事,下来我们就开始分析第二件事了,也就是该驱动调用他的probe函数。cfi_probe函数:struct mtd_info *cfi_probe(struct map_info *map)
{
/*
* Just use the generic probe stuff to call our CFI-specific
* chip_probe routine in all the possible permutations, etc.
*//*使用gen_probe.c中的函数, cfi_chip_probe数据结构实现探测 */
return mtd_do_chip_probe(map, &cfi_chip_probe);(driversmtdchipsgen_probe.c)
}
而mtd_do_chip_probe(map, &cfi_chip_probe);函数所做的是:/* 探测芯片是否有CFI. */
cfi = genprobe_ident_chips(map, cp);
                    genprobe_new_chip(map, cp, &cfi)
                            cp->probe_chip(map, 0, NULL, cfi)
而此处的cp就是mtd_do_chip_probe(map, &cfi_chip_probe)函数的第二个参数cfi_chip_probe,而他的probe_chip函数有:/* This is the first time we're called. Set up the CFI stuff accordingly and return */return cfi_chip_setup(map, cfi);( driversmtdchipscfi_probe.c) 
上面cfi_chip_setup就是通过发送命令来进入CFI,并读出设备相关信息:cfi_send_gen_cmd(0xf0,     0, base, map, cfi, cfi->device_type, NULL);
cfi_send_gen_cmd(0xaa, 0x555, base, map, cfi, cfi->device_type, NULL);
cfi_send_gen_cmd(0x55, 0x2aa, base, map, cfi, cfi->device_type, NULL);
cfi_send_gen_cmd(0x90, 0x555, base, map, cfi, cfi->device_type, NULL);
cfi->mfr = cfi_read_query16(map, base);
cfi->id = cfi_read_query16(map, base + ofs_factor);        /* Do any necessary byteswapping */
cfi->cfiq->P_ID = le16_to_cpu(cfi->cfiq->P_ID);
cfi->cfiq->P_ADR = le16_to_cpu(cfi->cfiq->P_ADR);
cfi->cfiq->A_ID = le16_to_cpu(cfi->cfiq->A_ID);
cfi->cfiq->A_ADR = le16_to_cpu(cfi->cfiq->A_ADR);
cfi->cfiq->InterfaceDesc = le16_to_cpu(cfi->cfiq->InterfaceDesc);
cfi->cfiq->MaxBufWriteSize = le16_to_cpu(cfi->cfiq->MaxBufWriteSize);
通过上一篇文章的对CFI命令的介绍我们知道上面就是获得CFI的信息的命令和地址。
我们现在讲第二种规范:jedec。这个规范其实与上一个在层次上大致一样,所以我们就不像上面讲的那么细:probe_type =" jedec_probe"
do_map_probe(*probe_type, &info->map)(driversmtdchipschipreg.c)
drv = get_mtd_chip_driver(name);   /* name = "jedec_probe" */
list_for_each(pos, &chip_drvs_list) {
this = list_entry(pos, typeof(*this), list);
if (!strcmp(this->name, name)) {
ret = this;
break;
}
}

ret = drv->probe(map);


chip_drvs_list有谁定义?
由:
void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{
spin_lock(&chip_drvs_lock);
list_add(&drv->list, &chip_drvs_list);
spin_unlock(&chip_drvs_lock);
}
那又是谁调用函数register_mtd_chip_driver??
由:
static int __init jedec_probe_init(void) (driversmtdchipsjedec_probe.c)
{
register_mtd_chip_driver(&jedec_chipdrv);
static struct mtd_chip_driver jedec_chipdrv = {
.probe = jedec_probe,
( driversmtdchipsgen_probe.c)mtd_do_chip_probe(map, &jedec_chip_probe);
cfi = genprobe_ident_chips(map, cp);
genprobe_new_chip(map, cp, &cfi)
cp->probe_chip(map, 0, NULL, cfi)
.name = "jedec_probe",
.module = THIS_MODULE
};
return 0;
}


jedec_chip_probe (driversmtdchipsjedec_probe.c)
static struct chip_probe jedec_chip_probe = {
.name = "JEDEC",
.probe_chip = jedec_probe_chip
    if(cfi->addr_unlock1) {
        cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
        cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL);
    }
        cfi_send_gen_cmd(0x90, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);        cfi->mfr = jedec_read_mfr(map, base, cfi);
        cfi->id = jedec_read_id(map, base, cfi);
};jedec_match( base, map, cfi, &jedec_table[i] )
只不过此处与上面不同的是通过获得的厂家ID和设备ID再与jedec_table中的厂家ID和设备ID对比,其中:static const struct amd_flash_info jedec_table[] = { (driversmtdchipsjedec_probe.c)
{
.mfr_id = MANUFACTURER_MACRONIX,
.dev_id = MX29LV160B,
.name = "MXIC MX29LV160B",
.uaddr = {
[0] = MTD_UADDR_0x0AAA_0x0555,  /* x8 */
[1] = MTD_UADDR_0x0555_0x02AA,  /* x16 ,16位的解锁地址为0x0555_0x02AA*/ 
},
.DevSize = SIZE_2MiB,                                /* 芯片大小为2MBYTE */
.CmdSet = P_ID_AMD_STD,              /* 使用那些命令 :0x0020*/
.NumEraseRegions= 4,                               /* 有四种不同的扇区 ,分别为下面:*/
.regions = {
ERASEINFO(0x04000,1),                     /* 一个16KBYTE */  
ERASEINFO(0x02000,2),                   /* 两个8KBYTE */  
ERASEINFO(0x08000,1),                  /* 一个32KBYTE */  
ERASEINFO(0x10000,31)                /* 31个64KBYTE */  
}
}}我们知道nor协议层的作用是:Mtd_info:知道向什么地址发什么数据来擦除,读写而硬件相关层的作用是Map_info:(最小差异)他知道基地址,位宽那么怎么知道基地址和位宽就可以知道向什么地址发什么数据来擦除,读写那?而他们的关系又是什么那,我们要讨论mtd_info和map_info的关系,就逃不开一个函数:struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp),他通过他内部的mtd = check_cmd_set(map, 1)函数,通过map_info来确定mtd_info。来实现知道了基地址和位宽就可以知道向什么地址发什么数据来擦除,读写。
static struct mtd_info *check_cmd_set(struct map_info *map, int primary)
{
struct cfi_private *cfi = map->fldrv_priv;
__u16 type = primary?cfi->cfiq->P_ID:cfi->cfiq->A_ID;
switch(type){
/* We need these for the !CONFIG_MODULES case,
  because symbol_get() doesn't work there */
#ifdef CONFIG_MTD_CFI_INTELEXT
case 0x0001:
case 0x0003: