当前位置: IT大杂烩 > Ubuntu  > 【详解】如何编写Linux下Nand Flash驱动

【详解】如何编写Linux下Nand Flash驱动

www.someabcd.com  网友分享于:Jun 8, 2018 6:09:02 PM

标签:des   style   blog   class   code   tar   

From: http://www.crifan.com/files/doc/docbook/linux_nand_driver/release/html/linux_nand_driver.html

版本:v2.2

 

Crifan Li

摘要

本文先解释了Nand Flash相关的一些名词,再从Flash硬件机制开始,介绍到Nand Flash的常见的物理特性,且深入介绍了Nand Flash的一些高级功能,然后开始介绍Linux下面和Nand Flash相关的软件架构MTD的相关知识,最后介绍了在Linux的MTD驱动框架下,如何实现Nand Flash的驱动。

2013-04-07

修订历史
修订 1.0 2009-07-21 crl
  1. 简介如何在Linux下实现Nand Flash驱动
修订 1.2 2011-03-15 crl
  1. 整理了排版
  2. 添加了很多内容
修订 1.3 2011-06-12 crl
  1. 修正了Nand Flash行列地址的计算方法
修订 1.7 2011-07-02 crl
  1. 添加了ONFI,LBA规范的介绍
  2. 添加了Unique ID介绍
  3. 添加了对应的MTD中检测不同类型芯片的代码
  4. 增加了关于Nand Flash的软件和硬件的ECC算法的简介
修订 1.8 2011-10-04 crl
  1. 添加了Nand Flash位翻转的详细介绍
  2. 添加了Nand Flash的结构图
修订 1.9 2012-06-14 crl
  1. 通过Docbook发布
修订 2.2 2013-04-07 crl
  1. 修正关于Nand Flash物理架构组成总容量方面的解释
  2. 修正其他一些笔误
  3. 添加了和Nand Flash相关的一些资料,比如id的命名规则
  4. 添加了keyword,第二章所有章节的id
  5. 修正了笔误,把"第7000个块中的第25页中的1208字节处"改为"第7000个块中的第64页中的1208字节处",以及同步修正了相关的数字

目录

缩略词
正文之前
1. 目的
2. 目标读者和阅读此文的前提
3. 说明
4. 声明
1. 编写驱动之前要了解的知识
1.1. 一些相关的名词的解释
1.1.1. Non-Volatile Memory非易失性存储器
1.1.2. OTP一次性可编程存储器
1.1.3. NDA 保密协议
1.1.4. Datasheet数据手册和Specification规范
1.1.5. Nand Flash相关的一些名词解释
1.1.5.1. (Bad) Block Management(坏)块管理
1.1.5.2. Wear-Leveling负载平衡
1.1.5.3. ECC错误校验码
1.2. 硬件特性
1.2.1. 什么是Flash
1.2.1.1. Flash的硬件实现机制
1.2.2. 什么是Nand Flash
1.2.2.1. Nand Flash和Nor Flash的区别
1.2.2.2. Nand Flash的详细分类
1.2.3. SLC和MLC的实现机制
1.2.3.1. SLC(Single Level Cell)
1.2.3.2. MLC(Multi Level Cell)
1.2.3.3. 关于如何识别SLC还是MLC
1.2.4. Nand Flash数据存储单元的整体架构
1.2.5. Nand Flash的物理存储单元的阵列组织结构
1.2.5.1. Block块
1.2.5.2. Page页
1.2.5.3. oob / Redundant Area / Spare Area
1.2.6. Flash名称的由来
1.2.7. Flash相对于普通设备的特殊性
1.2.8. Nand Flash的位反转特性
1.2.8.1. Nand Flash位反转的原因
1.2.8.2. Nand Flash位反转的影响
1.2.8.3. Nand Flash位反转的类型和解决办法
1.2.9. Nand Flash引脚(Pin)的说明
1.2.9.1. 为何需要ALE和CLE
1.2.9.2. Nand Flash只有8个I/O引脚的好处
1.2.9.2.1. 减少外围连线
1.2.9.2.2. 提高系统的可扩展性
1.2.10. Nand Flash的一些典型(typical)的特性
1.2.11. Nand Flash控制器与Nand Flash芯片
1.2.12. Nand Flash中的特殊硬件结构
1.2.13. Nand Flash中的坏块(Bad Block)
1.2.13.1. 坏块的分类
1.2.13.2. 坏块的标记
1.2.13.3. 坏块的管理
1.2.13.4. 坏块的比例
1.2.14. Nand Flash中页的访问顺序
1.2.15. 常见的Nand Flash的操作
1.2.15.1. 页编程(Page Program)注意事项
1.2.15.2. 读(Read)操作过程详解
1.2.15.2.1. 需要使用何种命令
1.2.15.2.2. 发送命令前的准备工作以及时序图各个信号的具体含义
1.2.15.2.3. 如何计算出我们要传入的行地址和列地址
1.2.15.2.4. 读操作过程的解释
1.2.16. Nand Flash的一些高级特性
1.2.16.1. Nand Flash的Unique ID
1.2.16.1.1. 什么是Unique ID唯一性标识
1.2.16.1.2. 不同Nand Flash厂商的对Unique ID的不同的实现方法
1.2.16.1.2.1. Toshiba东芝的Nand的Unique ID
1.2.16.1.2.2. 读取Toshiba的Nand的Unique ID
1.2.16.1.3. Samsung三星的Nand的Unique ID
1.2.16.1.3.1. 读取Samsung的Nand的Unique ID
1.2.16.1.4. 遵循ONFI规范的厂商的Nand的Unique ID
1.2.16.1.4.1. 读取遵循ONFI的厂商的Nand的Unique ID
1.2.16.2. 片选无关(CE don’t-care)技术
1.2.16.3. 带EDC的拷回操作以及Sector的定义(Copy-Back Operation with EDC & Sector Definition for EDC)
1.2.16.4. 多片同时编程(Simultaneously Program Multi Plane)
1.2.16.5. 交错页编程(Interleave Page Program)
1.2.16.6. 随机输出页内数据(Random Data Output In a Page)
1.3. 软件方面
1.3.1. Nand Flash相关规范 – ONFI和LBA
1.3.1.1. ONFI是什么
1.3.1.1.1. ONFI Block Abstracted NAND
1.3.1.1.2. ONFI的好处
1.3.1.2. LBA规范是什么
1.3.1.3. 为何会有ONFI和LBA
1.3.1.3.1. 技术层面的解释
1.3.1.3.2. 现实层面的解释
1.3.1.4. ONFI和LBA的区别和联系
1.3.1.4.1. ONFI和LBA的区别
1.3.1.4.2. ONFI和LBA的联系
1.3.2. 内存技术设备,MTD(Memory Technology Device)
1.3.2.1. Linux MTD中检测不同类型Nand Flash的ID部分的代码
1.3.3. 读操作的硬件到软件的映射
1.3.4. Nand flash驱动工作原理
2. Linux下Nand Flash驱动编写步骤简介
2.1. 对于驱动框架部分
2.1.1. 对于Nand Flash底层操作实现部分
3. 和Nand Flash相关的一些资料
3.1. Nand Flash的型号/Part Number/Id的命名规则
3.1.1. 三星(Samsung)的Nand Flash芯片的命名规则
3.1.2. 美光(Micron)的Nand Flash芯片的命名规则
3.1.3. 海力士(Hynix)的Nand Flash芯片的命名规则
参考书目

目录

1.1. 一些相关的名词的解释
1.1.1. Non-Volatile Memory非易失性存储器
1.1.2. OTP一次性可编程存储器
1.1.3. NDA 保密协议
1.1.4. Datasheet数据手册和Specification规范
1.1.5. Nand Flash相关的一些名词解释
1.1.5.1. (Bad) Block Management(坏)块管理
1.1.5.2. Wear-Leveling负载平衡
1.1.5.3. ECC错误校验码
1.2. 硬件特性
1.2.1. 什么是Flash
1.2.1.1. Flash的硬件实现机制
1.2.2. 什么是Nand Flash
1.2.2.1. Nand Flash和Nor Flash的区别
1.2.2.2. Nand Flash的详细分类
1.2.3. SLC和MLC的实现机制
1.2.3.1. SLC(Single Level Cell)
1.2.3.2. MLC(Multi Level Cell)
1.2.3.3. 关于如何识别SLC还是MLC
1.2.4. Nand Flash数据存储单元的整体架构
1.2.5. Nand Flash的物理存储单元的阵列组织结构
1.2.5.1. Block块
1.2.5.2. Page页
1.2.5.3. oob / Redundant Area / Spare Area
1.2.6. Flash名称的由来
1.2.7. Flash相对于普通设备的特殊性
1.2.8. Nand Flash的位反转特性
1.2.8.1. Nand Flash位反转的原因
1.2.8.2. Nand Flash位反转的影响
1.2.8.3. Nand Flash位反转的类型和解决办法
1.2.9. Nand Flash引脚(Pin)的说明
1.2.9.1. 为何需要ALE和CLE
1.2.9.2. Nand Flash只有8个I/O引脚的好处
1.2.9.2.1. 减少外围连线
1.2.9.2.2. 提高系统的可扩展性
1.2.10. Nand Flash的一些典型(typical)的特性
1.2.11. Nand Flash控制器与Nand Flash芯片
1.2.12. Nand Flash中的特殊硬件结构
1.2.13. Nand Flash中的坏块(Bad Block)
1.2.13.1. 坏块的分类
1.2.13.2. 坏块的标记
1.2.13.3. 坏块的管理
1.2.13.4. 坏块的比例
1.2.14. Nand Flash中页的访问顺序
1.2.15. 常见的Nand Flash的操作
1.2.15.1. 页编程(Page Program)注意事项
1.2.15.2. 读(Read)操作过程详解
1.2.15.2.1. 需要使用何种命令
1.2.15.2.2. 发送命令前的准备工作以及时序图各个信号的具体含义
1.2.15.2.3. 如何计算出我们要传入的行地址和列地址
1.2.15.2.4. 读操作过程的解释
1.2.16. Nand Flash的一些高级特性
1.2.16.1. Nand Flash的Unique ID
1.2.16.1.1. 什么是Unique ID唯一性标识
1.2.16.1.2. 不同Nand Flash厂商的对Unique ID的不同的实现方法
1.2.16.1.2.1. Toshiba东芝的Nand的Unique ID
1.2.16.1.2.2. 读取Toshiba的Nand的Unique ID
1.2.16.1.3. Samsung三星的Nand的Unique ID
1.2.16.1.3.1. 读取Samsung的Nand的Unique ID
1.2.16.1.4. 遵循ONFI规范的厂商的Nand的Unique ID
1.2.16.1.4.1. 读取遵循ONFI的厂商的Nand的Unique ID
1.2.16.2. 片选无关(CE don’t-care)技术
1.2.16.3. 带EDC的拷回操作以及Sector的定义(Copy-Back Operation with EDC & Sector Definition for EDC)
1.2.16.4. 多片同时编程(Simultaneously Program Multi Plane)
1.2.16.5. 交错页编程(Interleave Page Program)
1.2.16.6. 随机输出页内数据(Random Data Output In a Page)
1.3. 软件方面
1.3.1. Nand Flash相关规范 – ONFI和LBA
1.3.1.1. ONFI是什么
1.3.1.1.1. ONFI Block Abstracted NAND
1.3.1.1.2. ONFI的好处
1.3.1.2. LBA规范是什么
1.3.1.3. 为何会有ONFI和LBA
1.3.1.3.1. 技术层面的解释
1.3.1.3.2. 现实层面的解释
1.3.1.4. ONFI和LBA的区别和联系
1.3.1.4.1. ONFI和LBA的区别
1.3.1.4.2. ONFI和LBA的联系
1.3.2. 内存技术设备,MTD(Memory Technology Device)
1.3.2.1. Linux MTD中检测不同类型Nand Flash的ID部分的代码
1.3.3. 读操作的硬件到软件的映射
1.3.4. Nand flash驱动工作原理

摘要

Nand Flash物理特性上使得其数据读写过程中会发生一定几率的错误,所以要有个对应的错误检测和纠正的机制,于是才有此ECC,用于数据错误的检测与纠正。Nand Flash的ECC,常见的算法有海明码和BCH,这类算法的实现,可以是软件也可以是硬件。不同系统,根据自己的需求,采用对应的软件或者是硬件。

相对来说,硬件实现这类ECC算法,肯定要比软件速度要快,但是多加了对应的硬件部分,所以成本相对要高些。如果系统对于性能要求不是很高,那么可以采用软件实现这类ECC算法,但是由于增加了数据读取和写入前后要做的数据错误检测和纠错,所以性能相对要降低一些,即Nand Flash的读取和写入速度相对会有所影响。

其中,Linux中的软件实现ECC算法,即NAND_ECC_SOFT模式,就是用的对应的海明码。

而对于目前常见的MLC的Nand Flash来说,由于容量比较大,动辄2GB,4GB,8GB等,常用BCH算法。BCH算法,相对来说,算法比较复杂。

笔者由于水平有限,目前仍未完全搞懂BCH算法的原理。

BCH算法,通常是由对应的Nand Flash的Controller中,包含对应的硬件BCH ECC模块,实现了BCH算法,而作为软件方面,需要在读取数据后,写入数据之前,分别操作对应BCH相关的寄存器,设置成BCH模式,然后读取对应的BCH状态寄存器,得知是否有错误,和生成的BCH校验码,用于写入。

其具体代码是如何操作这些寄存器的,由于是和具体的硬件,具体的nand flash的controller不同而不同,无法用同一的代码。如果你是nand flash驱动开发者,自然会得到对应的起nand flash的controller部分的datasheet,按照手册说明,去操作即可。

不过,额外说明一下的是,关于BCH算法,往往是要从专门的做软件算法的厂家购买的,但是Micron之前在网上放出一个免费版本的BCH算法。

想要此免费的BCH算法,可以在[17]找到下载地址

Flash全名叫做Flash Memory,从名字就能看出,是种数据存储设备,存储设备有很多类,Flash属于非易失性存储设备(Non-volatile Memory Device),与此相对应的是易失性存储设备(Volatile Memory Device)。关于什么是非易失性/易失性,从名字中就可以看出,非易失性就是不容易丢失,数据存储在这类设备中,即使断电了,也不会丢失,这类设备,除了Flash,还有其他比较常见的入硬盘,ROM等,与此相对的,易失性就是断电了,数据就丢失了,比如大家常用的内存,不论是以前的SDRAM,DDR SDRAM,还是现在的DDR2,DDR3等,都是断电后,数据就没了。

Flash主要分两种,Nand Flash和nor flash。

关于Nand Flash和Nor Flash的区别,参见[5]

不过,关于两者区别,除了那个解释之外,这里再多解释解释:

  1. Nor的成本相对高,容量相对小,比如常见的只有128KB,256KB,1MB,2MB等等,优点是读写数据时候,不容易出错。所以在应用领域方面,Nor Flash比较适合应用于存储少量的代码。

  2. Nand flash成本相对低,说白了就是便宜,缺点是使用中数据读写容易出错,所以一般都需要有对应的软件或者硬件的数据校验算法,统称为ECC。但优点是,相对来说容量比较大,现在常见的Nand Flash都是1GB,2GB,更大的8GB的都有了,相对来说,价格便宜,因此适合用来存储大量的数据。其在嵌入式系统中的作用,相当于PC上的硬盘,用于存储大量数据。

所以,一个常见的应用组合就是,用小容量的Nor Flash存储启动代码,比如uboot,用大容量的Nand Flash做整个系统和用户数据的存储。

而一般的嵌入式平台的启动流程也就是,系统从装有启动代码的Nor Flash启动后,初始化对应的硬件,包括SDRAM等,然后将Nand Flash上的Linux 内核读取到内存中,做好该做的事情后,就跳转到SDRAM中去执行内核了,然后内核解压(如果是压缩内核的话,否则就直接运行了)后,开始运行,在Linux内核启动最后,去Nand Flash上,挂载根文件,比如jffs2,yaffs2等,挂载完成,运行初始化脚本,启动consle交互,才允许你通过console和内核交互。至此完成整个系统启动过程。

而Nor Flash就分别存放的是Uboot,Nand Flash存放的是Linux的内核镜像和根文件系统,以及余下的空间分成一个数据区。

Nand Flash按照内部存储数据单元的电压的不同层次,也就是单个内存单元中,是存储1位数据,还是多位数据,可以分为SLC和MLC。

简单说就是,常见的Nand Flash,内部只有一个chip,每个chip只有一个plane。

而有些复杂的,容量更大的Nand Flash,内部有多个chip,每个chip有多个plane。这类的Nand Flash,往往也有更加高级的功能,比如下面要介绍的Multi Plane Program和Interleave Page Program等。

概念上,由大到小来说,就是:

Nand Flash ? Chip ? Plane ? Block ? Page ? oob

用图表来表示,更加易懂:


比如,型号为K9K8G08U0A这块Nand Flash(有时候也被称为此块chip芯片),其内部有两个K9F4G08U0A的chip,chip#1和chip#2,每个K9F4G08U0A的chip包含了2个Plane,每个Plane是2Gbbit,所以K9F4G08U0A的大小是2Gb×2 = 4Gb = 512MB,因此,K9K8G08U0A内部有2个K9F4G08U0A,或者说4个Plane,总大小是×256MB=1GB。

用公式表示如下:


而型号是K9WAG08U1A的Nand Flash,内部包含了2个K9K8G08U0A,所以,总容量是K9K8G08U0A的两倍=1GB×2=2GB,类似地K9NBG08U5A,内部包含了4个K9K8G08U0A,总大小就是4×1GB=4GB。

[注意] 通常只关心Nand的总大小

上面所说的block,page等Nand Flash的物理上的组织结构,是在chip的基础上来说的,但是软件编程的时候,除非你要用到Multi Plane Program和Interleave Page Program等,一般很少区分内部有几个chip以及每个chip有几个plane,而最关心的只是Nand Flash的总体容量size有多大,比如是1GB还是2GB等等。

下面详细介绍一下,Nand Flash的一个chip内部的硬件逻辑组织结构。

Nand Flash的内部组织结构,此处还是用图来解释,比较容易理解:


上图是K9K8G08U0A的datasheet中的描述。

简单解释就是:

根据上面提到过的,Flash最小操作单位,相对于普通存储设备,就显得有些特殊。

因为一般存储设备,比如硬盘或内存,读取和写入都是以位(bit)为单位,读取一个bit的值,将某个值写入对应的地址的位,都是可以按位操作的。

但是Flash由于物理特性,使得内部存储的数据,只能从1变成0,这点,这点可以从前面的内部实现机制了解到,对于最初始值,都是1,所以是0xFFFFFFFF,而数据的写入,即是将对应的变成0,而将数据的擦出掉,就是统一地,以block为单位,全部一起充电,所有位,都变成初始的1,而不是像普通存储设备那样,每一个位去擦除为0。而数据的写入,就是电荷放电的过程,代表的数据也从1变为了0。

所以,总结一下Flash的特殊性如下:


Nand Flash的位反转,也叫做位翻转,对应的英文表达有:Bit Flip=Bit Flipping=Bit-Flip=Bit twiddling。

Nand Flash由于本身硬件的内在特性,会导致(极其)偶尔的出现位反转的现象。

所谓的位反转,bit flip,指的是原先Nand Flash中的某个位,变化了,即要么从1变成0了,要么从0变成1了。

对应的位反转的类型,有两种:

  1. 一种是nand flash物理上的数据存储的单元上的数据,是正确的,只是在读取此数据出来的数据中的某位,发生变化,出现了位反转,即读取出来的数据中,某位错了,本来是0变成1,或者本来是1变成0了。此处可以成为软件上位反转。此数据位的错误,当然可以通过一定的校验算法检测并纠正。
  2. 另外一种,就是nand flash中的物理存储单元中,对应的某个位,物理上发生了变化,原来是1的,变成了0,或原来是0的,变成了1,发生了物理上的位的数据变化。此处可以成为硬件上的位反转。此错误,由于是物理上发生的,虽然读取出来的数据的错误,可以通过软件或硬件去检测并纠正过来,但是物理上真正发生的位的变化,则没办法改变了。不过个人理解,好像也是可以通过擦除Erase整个数据块Block的方式去擦除此错误,不过在之后的Nand Flash的使用过程中,估计此位还是很可能继续发生同样的硬件的位反转的错误。

以上两种类型的位反转,其实对于从Nand Flash读取出来的数据来说,解决其中的错误的位的方法,都是一样的,即通过一定的校验算法,常称为ECC,去检测出来,或检测并纠正错误。

如果只是单独检测错误,那么如果发现数据有误,那么再重新读取一次即可。

实际中更多的做法是,ECC校验发现有错误,会有对应的算法去找出哪位错误并且纠正过来。

其中对错误的检测和纠正,具体的实现方式,有软件算法,也有硬件实现,即硬件Nand Flash的控制器controller本身包含对应的硬件模块以实现数据的校验和纠错的。


上图是常见的Nand Flash所拥有的引脚(Pin)所对应的功能,简单翻译如下:


[提示] 数据手册中的#表示低电平

在数据手册中,你常会看到,对于一个引脚定义,有些字母上面带一横杠的,那是说明此引脚/信号是低电平有效,比如你上面看到的RE头上有个横线,就是说明,此RE是低电平有效,此外,为了书写方便,在字母后面加“#”,也是表示低电平有效,比如我上面写的CE#;如果字母头上啥都没有,就是默认的高电平有效,比如上面的CLE,就是高电平有效。

在Nand Flash的硬件设计中,你会发现很多个引脚。关于硬件上为何设计这样的引脚,而不是直接像其他存储设备,比如普通的RAM,直接是一对数据线引出来,多么方便和好理解啊。

关于这样设计的好处:

Nand Flash中,一个块中含有1个或多个位是坏的,就称为其为坏块Bad Block。

坏块的稳定性是无法保证的,也就是说,不能保证你写入的数据是对的,或者写入对了,读出来也不一定对的。与此对应的正常的块,肯定是写入读出都是正常的。

具体标记的地方是,对于现在常见的页大小为2K的Nand Flash,是块中第一个页的oob起始位置(关于什么是页和oob,下面会有详细解释)的第1个字节(旧的小页面,pagesize是512B甚至256B的Nand Flash,坏块标记是第6个字节),如果不是0xFF,就说明是坏块。相对应的是,所有正常的块,好的块,里面所有数据都是0xFF的。

不过,对于现在新出的有些Nand Flash,很多标记方式,有些变化,有的变成该坏块的第一个页或者第二个页,也有的是,倒数最后一个或倒数第二个页,用于标记坏块的。

具体的信息,请参考对应的Nand Flash的数据手册,其中会有说明。

对于坏块的标记,本质上,也只是对应的flash上的某些字节的数据是非0xFF而已,所以,只要是数据,就是可以读取和写入的。也就意味着,可以写入其他值,也就把这个坏块标记信息破坏了。对于出厂时的坏块,一般是不建议将标记好的信息擦除掉的。

uboot中有个命令是

nand scrub

就可以将块中所有的内容都擦除了,包括坏块标记,不论是出厂时的,还是后来使用过程中出现而新标记的。一般来说,不建议用这个。

不过,在实际的驱动编程开发过程中,为了方便起见,我倒是经常用,其实也没啥大碍,呵呵。不过呢,其实最好的做法是,用

nand erase

只擦除好的块,对于已经标记坏块的块,不要轻易擦除掉,否则就很难区分哪些是出厂时就坏的,哪些是后来使用过程中用坏的了。

要实现对Nand Flash的操作,比如读取一页的数据,写入一页的数据等,都要发送对应的命令,而且要符合硬件的规定,如图:


从上图可以看到,如果要实现读一个页的数据,就要发送Read的命令,而且是分两个周期(Cycle),即分两次发送对应的命令,第一次是0x00h,第二次是0x30h,而两次命令中间,需要发送对应的你所要读取的页的地址,关于此部分详细内容,留待后表。

对应地,其他常见的一些操作,比如写一个页的数据(Page Program),就是先发送0x80h,然后发生要写入的地址,再发送0x10h。

[注意] 注意

对于不同厂家的不同型号的Nand Flash 的基本操作,即读页数据Read Page,写页数据(对页进行编程)Page Program,擦除整个块的数据Erase Block等操作,所用的命令都是一样的,但是针对一些Nand Flash的高级的一些特性,比如交错页编程(Interleave Page Program),多片同时编程(Simultaneously Program Multi Plane)等,所用的命令,未必一样,不过对于同一厂家的Nand Flash的芯片,那一般来说,都是统一的。

关于一些常见的操作,比如读一个页的Read操作和写一个页的Page Program,下面开始更深入的介绍。

下面以最简单的read操作为例,解释如何理解时序图,以及将时序图中的要求,转化为代码。

解释时序图之前,让我们先要搞清楚,我们要做的事情:

从Nand Flash的某个页Page里面,读取我们要的数据。

要实现此功能,会涉及到几部分的知识,即使我们不太懂Nand Flash的细节,但是通过前面的基本知识的介绍,那么以我们的常识,至少很容易想到的就是,需要用到哪些命令,怎么发这些命令,怎么计算所需要的地址,怎么读取我们要的数据等等。

下面就一步步的解释,需要做什么,以及如何去做:

知道了用何命令后,再去了解如何发送这些命令。

[提示] 提示

在开始解释前,关于”使能”这个词要罗嗦一下,以防有些读者和我以前一样,在听这类词语的时候,属于初次接触,或者接触不多的,就很容易被搞得一头雾水的(虽然该词汇对于某些专业人士说是属于最基本的词汇了,囧)。

使能(Enable),是指使其(某个信号)有效,使其生效的意思,“使其”“能够”怎么怎么样。。。。比如,上面图中的CLE线号,是高电平有效,如果此时将其设为高电平,我们就叫做,将CLE使能,也就是使其生效的意思。


[注意] 注意
此图来自三星的型号K9K8G08U0A的Nand Flash的数据手册(datasheet)。

我们来一起看看,我在图6中的特意标注的①边上的黄色竖线。

黄色竖线所处的时刻,是在发送读操作的第一个周期的命令0x00之前的那一刻。

让我们看看,在那一刻,其所穿过好几行都对应什么值,以及进一步理解,为何要那个值。

  1. 黄色竖线穿过的第一行,是CLE。还记得前面介绍命令所存使能(CLE)那个引脚吧?CLE,将CLE置1,就说明你将要通过I/O复用端口发送进入Nand Flash的,是命令,而不是地址或者其他类型的数据。只有这样将CLE置1,使其有效,才能去通知了内部硬件逻辑,你接下来将收到的是命令,内部硬件逻辑,才会将受到的命令,放到命令寄存器中,才能实现后面正确的操作,否则,不去将CLE置1使其有效,硬件会无所适从,不知道你传入的到底是数据还是命令了。
  2. 而第二行,是CE#,那一刻的值是0。这个道理很简单,你既然要向Nand Flash发命令,那么先要选中它,所以,要保证CE#为低电平,使其有效,也就是片选有效。
  3. 第三行是WE#,意思是写使能。因为接下来是往Nand Flash里面写命令,所以,要使得WE#有效,所以设为低电平。
  4. 第四行,是ALE是低电平,而ALE是高电平有效,此时意思就是使其无效。而对应地,前面介绍的,使CLE有效,因为将要数据的是命令(此时是发送图示所示的读命令第二周期的0x30),而不是地址。如果在其他某些场合,比如接下来的要输入地址的时候,就要使其有效,而使CLE无效了。
  5. 第五行,RE#,此时是高电平,无效。可以看到,知道后面低6阶段,才变成低电平,才有效,因为那时候,要发生读取命令,去读取数据。
  6. 第六行,就是我们重点要介绍的,复用的输入输出I/O端口了,此刻,还没有输入数据,接下来,在不同的阶段,会输入或输出不同的数据/地址。
  7. 第七行,R/B#,高电平,表示R(Ready)/就绪,因为到了后面的第5阶段,硬件内部,在第四阶段,接受了外界的读取命令后,把该页的数据一点点送到页寄存器中,这段时间,属于系统在忙着干活,属于忙的阶段,所以,R/B#才变成低,表示Busy忙的状态的。

介绍了时刻①的各个信号的值,以及为何是这个值之后,相信,后面的各个时刻,对应的不同信号的各个值,大家就会自己慢慢分析了,也就容易理解具体的操作顺序和原理了。

在介绍具体读取数据的详细流程之前,还要做一件事,那就是,先要搞懂我们要访问的地址,以及这些地址,如何分解后,一点点传入进去,使得硬件能识别才行。

此处还是以K9K8G08U0A为例,此Nand Flash,一共有8192个块,每个块内有64页,每个页是2K+64 Bytes。

假设,我们要访问其中的第7000个块中的第64页中的1208字节处的地址,此时,我们就要先把具体的地址算出来:

物理地址

=块大小×块号 + 页大小×页号 + 页内地址

=128K×7000 + 2K×64 + 1208

=0x36B204B8

接下来,我们就看看,怎么才能把这个实际的物理地址,转化为Nand Flash所要求的格式。

在解释地址组成之前,先要来看看其datasheet中关于地址周期的介绍:


结合图 1.7 “Nand Flash数据读取操作的时序图”中的2,3阶段,我们可以看出,此Nand Flash地址周期共有5个,2个列(Column)周期,3个行(Row)周期。

  1. 对应地,列地址A0~A10,就是页内地址,地址范围是从0到2047。

    细心的读者可能注意到了,为何此处多出来个A11呢?

    这样从A0到A11,一共就是12位,可以表示的范围就是0~212,即0~4096了。

    实际上,由于我们访问页内地址,可能会访问到oob的位置,即2048-2111这64个字节的范围内,所以,此处实际上只用到了2048~2111,用于表示页内的oob区域,其大小是64字节。

  2. 对应地,A12~A30,称作页号,页的号码,可以定位到具体是哪一个页。

    A18~A30,表示对应的块号,即属于哪个块。

简单解释完了地址组成,那么就很容易分析上面例子中的地址了。

注意,下面这样的方法,是错误的:

0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000,分别分配到5个地址周期就是:

1st周期 A7 ~ A0 1011 1000 = 0xB8
2nd周期 A11~ A8 0100 = 0x04
3rd周期 A19~A12 0010 0000 = 0x20
4th周期 A27~A20 0110 1011 = 0x6B
5th周期 A30~A28 11 = 0x03

而至于上述计算方法为何是错误的,那是因为上面计算过程中,把第11位的值,本来是属于页号的位A11,给算成页内地址里面的值了。

应该是这样计算,才是对的:

0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000

1st周期 A7 ~ A0 1011 1000 = 0xB8
2nd周期 A10~ A8 100 = 0x04
3rd周期 A19~A12 010 0000 0 = 0x40
4th周期 A27~A20 110 1011 0 = 0xD6
5th周期 A30~A28 11 0 = 0x06

那有人会问了,上面表11中,不是明明写的A0到A30,其中包括A11,不是正好对应着此处地址中的bit0到bit30吗?

其实,我开始也是犯了同样的错误,误以为我们要传入的地址的每一位,就是对应着表11中的A0到A30呢,实际上,表11中的A11,是比较特殊的,只有当我们访问页内地址处于oob的位置,即属于2048~2111的时候,A11才会其效果,才会用A0-A11用来表示对应的某个属于2048~2111的某个值,属于oob的某个位置。

而我们此处的页内地址为2108,还没有超过2047呢,所以A11肯定是0。

 

这么解释,显得很绕,很难看懂。

换种方式来解释,就容易听懂了:

说白了,我们就是要访问第7000个块中的第64页中的1208字节处,对应着

页内地址

=1208

=0x4B8

 

页号

=块数×页数/块 + 块内的页号

= 7000×(128K/2K) + 64

= 7000×64 + 64

= 448064

=0x6D640

 

也就是,我们要访问0x6D640页内的0x4B8地址,这样很好理解吧,^_^。

然后对应的:

页内地址=0x4B8

分成两个对应的列地址,就变成

0x4B8 :列地址1=0xB8,列地址2=0x04

 

页号=0x6D640,分成三个行号就是:

0x6D640:行号1=0x40,行号2=0xD6,行号3=0x06

再回头看看上面的计算方法,

最开始计算出来的:

列地址1=0xB8

列地址2=0x04

行号1=0x20

行号2=0x6B

行号3=0x03

是错误的。

 

而第二次计算正确的:

列地址1=0xB8

列地址2=0x04

行号1=0x40

行号2=0xD6

行号3=0x06

才是对的,也和我们此处自己手动计算,是一致的。

第一次之所以计算错,就是错误的把行地址的最低一位A11,放到列地址中的最高位了。

至此,才算把如何手动计算行地址和列地址,解释明白和正确了。

对应的,Linux的源码\drivers\mtd\nand\nand_base.c中,也是这样处理的:

因此,我们要访问第7000个块中的第64页中的1208字节处的话,所要传入的地址就是分5个周期:

分别传入两个列地址的:

列地址1=0xB8

列地址2=0x04

然后再传3个行地址的:

行号1=0x40

行号2=0xD6

行号3=0x06

这样硬件才能识别。

而接下来的内容,也就是介绍硬件是如何处理这些输入的。

准备工作终于完了,下面就可以开始解释说明,对于读操作的,上面图中标出来的,1-6个阶段,具体是什么含义。

操作准备阶段:此处是读(Read)操作,所以,先发一个图5中读命令的第一个阶段的0x00,表示,让硬件先准备一下,接下来的操作是读。

发送两个周期的列地址。也就是页内地址,表示,我要从一个页的什么位置开始读取数据。

接下来再传入三个行地址。对应的也就是页号。

然后再发一个读操作的第二个周期的命令0x30。接下来,就是硬件内部自己的事情了。

Nand Flash内部硬件逻辑,负责去按照你的要求,根据传入的地址,找到哪个块中的哪个页,然后把整个这一页的数据,都一点点搬运到页缓存中去。而在此期间,你所能做的事,也就只需要去读取状态寄存器,看看对应的位的值,也就是R/B#那一位,是1还是0,0的话,就表示,系统是busy,仍在”忙“(着读取数据),如果是1,就说系统活干完了,忙清了,已经把整个页的数据都搬运到页缓存里去了,你可以接下来读取你要的数据了。

对于这里。估计有人会问了,这一个页一共2048+64字节,如果我传入的页内地址,就像上面给的1208一类的值,只是想读取1028到2011这部分数据,而不是页开始的0地址整个页的数据,那么内部硬件却读取整个页的数据出来,岂不是很浪费吗?答案是,的确很浪费,效率看起来不高,但是实际就是这么做的,而且本身读取整个页的数据,相对时间并不长,而且读出来之后,内部数据指针会定位到你刚才所制定的1208的那个位置。

接下来,就是你“窃取“系统忙了半天之后的劳动成果的时候了,呵呵。通过先去Nand Flash的控制器中的数据寄存器中写入你要读取多少个字节(byte)/字(word),然后就可以去Nand Flash的控制器的FIFO中,一点点读取你要的数据了。

至此,整个Nand Flash的读操作就完成了。

对于其他操作,可以根据我上面的分析,一点点自己去看datasheet,根据里面的时序图去分析具体的操作过程,然后对照代码,会更加清楚具体是如何实现的。

此处,继续解释之前,还要再次赘述一下:

目前Nand Flash的厂家有samsung,Toshiba,Intel, Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等。

由于前面所说的Nand Flash的规范之争,即Toshiba & Samsung和Intel + 其它厂商(Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等)之争,导致对于Unique ID这么个小的功能点(feature),不同的方面,弄出了不同的实现(做法)。

下面就来解释一下各个厂家关于Unique ID的实现方法,以及如何读取对应的Unique ID:

网上找到的某款三星的Nand的datasheet:

Samsung K9F5608U0B

http://hitmen.c02.at/files/docs/psp/ds_k9f5608u0b_rev13.pdf

6. Unique ID for Copyright Protection is available

- The device includes one block sized OTP (One Time Programmable), which can be used to increase system security or to provide identification capabilities. Detailed information can be obtained by contact with Samsung

 

即,Samsung的Nand的Unique ID,也和Toshiba的用途类似,也主要是用于版权保护,但是其实现却不同。

Samsung的Unique ID的实现,是专门在Nand 里面配备了一个OTP的Block,而此Nand芯片的Block大小是16KB。

而关于如何操作此OTP,即如何写入数据和读取数据,此处未说明。

欲知详情,请联系三星。

不过个人理解,应该和普通的block的操作类似,即普通的block,包含很多page,每个page的操作,有对应的page read,用对应的page read命令来读取此特殊的OTP的block里面的数据。

而此OTP的block里面的数据是什么,完全取决于自己最开始往里面写入了什么数据。说白了就是,你根据自己需求,在你的产品出厂的时候,写入对应的数据,比如该款产品的SN序列号等数据,然后自己在用page read读取出相应数据后,自己解析,得到自己要的信息,用于自己的用途,比如版权保护等。

主要指的是Intel英特尔,Hynix海力士,Micron美光,Numonyx恒亿,Spansion飞索等公司。

对应的Nand 的Unique ID的相关定义,ONFI的规范中都有,现简要摘录如下:

ONFI 2.2

http://onfi.org/wp-content/uploads/2009/02/ONFI%202_2%20Gold.pdf

ONFI规范中,在5.7.1. Parameter Page Data Structure Definition中,如图:


定义了一个page的数据,用于存储对应的Nand的各种参数,其中,有一个第8个字节的bit5==1的时候,表示支持“Read Unique ID”的命令,即说明此Nand芯片支持此命令,如果byte8的bit5==0,那么说明不支持,也就没法去读Unique ID了。

如果经过上述判断,此符合ONFI的Nand Flash支持Read Unique ID命令,次此时就可以通过该命令来读取对应的Nand Flash的Unique ID了。

此Read Unique ID的详细解释为:

 5.8. Read Unique ID Definition

The Read Unique ID function is used to retrieve the 16 byte unique ID (UID) for the device. The unique ID when combined with the device manufacturer shall be unique.

The UID data may be stored within the Flash array. To allow the host to determine if the UID is without bit errors, the UID is returned with its complement, as shown in Table 47. If the XOR of the UID and its bit-wise complement is all ones, then the UID is valid.

即用Read Unique ID命令来读取128bit=16字节的Unique ID,但是呢,为了用于防止写入的Unique ID有误,因此在16字节后面又添了个对应的补码,即每位取反的结果,这样前16字节的Unique ID和后16字节的Unique ID的补码,构成了32字节,算作一组,如下图所示:


To accommodate robust retrieval of the UID in the case of bit errors, sixteen copies of the UID and the corresponding complement shall be stored by the target. For example, reading bytes 32-63 returns to the host another copy of the UID and its complement. Read Status Enhanced shall not be used during execution of the Read Unique ID command

Figure 57 defines the Read Unique ID behavior. The host may use any timing mode supported by the target in order to retrieve the UID data.

而为了进一步防止出错,将上面32字节算一组,重复了16次,将这16个32字节的数据,存在Nand Flash里面,然后用Read Unique ID命令去读取出来,取得其中某个32字节即可,然后判断前16字节和后16字节取反,如果结果所有位都是1,那么结果即为16个0xFF,那么说明Unique ID是正确的。否则说明有误。

Read Unique ID的命令的详细格式如下图所示:


即先发送0xED命令,再发送0x00地址,然后等待Nand Flash的busy状态结束,就可以读取出来的那16组的32个字节了,然后用上面办法去判断,找到前16字节和后16字节异或得到结果全部16字节都为0xFF,即说明得到了正确的Unique ID。至此,符合ONFI规范的Unique ID,就读取出来了。

很多Nand flash支持一个叫做CE don’t-care的技术,字面意思就是,不关心是否片选。

对此也许有人会问了,如果不片选,那还能对其操作吗?答案就是,这个技术,主要用在当时是不需要选中芯片,但是芯片内部却仍可以继续操作的这些情况:在某些应用,比如录音,音频播放等应用中,外部使用的微秒(us)级的时钟周期,此处假设是比较少的2us,在进行读取一页或者对页编程时,是对Nand Flash操作,这样的串行(Serial Access)访问的周期都是20/30/50ns,都是纳秒(ns)级的,此处假设是50ns,当你已经发了对应的读或写的命令之后,接下来只是需要Nand Flash内部去自己操作,将数据读取除了或写入进去到内部的数据寄存器中而已,此处,如果可以把片选取消,CE#是低电平有效,取消片选就是拉高电平,这样会在下一个外部命令发送过来之前,即微秒量级的时间里面,即2us-50ns≈2us,这段时间的取消片选,可以降低很少的系统功耗,但是多次的操作,就可以在很大程度上降低整体的功耗了。

总的来说就是:由于某些外部应用所需要的访问Nand Flash的频率比较低,而Nand Flash内部操作速度比较快,所以在针对Nand Flash的读或写操作的大部分时间里面,都是在等待外部命令的输入,同时却选中芯片,产生了多余的功耗,此“不关心片选”技术,就是在Nand Flash的内部的相对快速的操作(读或写)完成之后,就取消片选,以节省系统功耗。待下次外部命令/数据/地址输入来的时候,再选中芯片,即可正常继续操作了。这样,整体上,就可以大大降低系统功耗了。

[提示] 提示
  1. 如果想要操作硬件Nand Flash芯片,先要将对应的CE#(低有效)片选信号拉低,选中该芯片,然后才能做接下来的读写操作所要做的发命令,发数据等动作。
  2. Nand Flash的片选与否,功耗差别会有很大。如果数据没有记错的话,我之前遇到我们系统里面的Nand Flash的片选,大概有5个mA的电流输出呢,也许你对5mA没太多概念,给你说个数据你就知道了:当时为了针对MP3播放功耗进行优化,整个系统优化之后的待机功耗,也才10个mA左右的,所以节省5mA已经算是很不错的功耗优化了。

在介绍此特性之前,先要说说,与Random Data Output In a Page相对应的是,普通的,正常的,sequential data output in a page。

正常情况下,我们读取数据,都是先发读命令,然后等待数据从存储单元到内部的页数据寄存器中后,我们通过不断地将RE#(Read Enale,低电平有效)置低,然后从我们开始传入的列的起始地址,一点点读出我们要的数据,直到页的末尾,当然有可能还没到页地址的末尾,就不再读了。所谓的顺序(sequential)读取也就是,根据你之前发送的列地址的起始地址开始,每读一个字节的数据出来,内部的数据指针就加1,移到下个字节的地址,然后你再读下一个字节数据,就可以读出来你要的数据了,直到读取全部的数据出来为止。

而此处的随机(random)读取,就是在你正常的顺序读取的过程中,先发一个随机读取的开始命令0x05命令,再传入你要将内部那个数据指针定位到具体什么地址,也就是2个cycle的列地址,然后再发随机读取结束命令0xE0,然后,内部那个数据地址指针,就会移动到你所制定的位置了,你接下来再读取的数据,就是从那个制定地址开始的数据了。

而Nand Flash数据手册里面也说了,这样的随机读取,你可以多次操作,没限制的。

请注意,上面你所传入的地址,都是列地址,也就是页内地址,也就是说,对于页大小为2K的Nand Flash来说,所传入的地址,应该是小于2048+64=2112的。

不过,实际在Nand Flash的使用中,好像这种用法很少的。绝大多数,都是顺序读取数据。

如果想要在Linux下编写Nand Flash驱动,那么就先要搞清楚Linux下,关于此部分的整个框架。弄明白,系统是如何管理你的Nand Flash的,以及,系统都帮你做了那些准备工作,而剩下的,驱动底层实现部分,你要去实现哪些功能,才能使得硬件正常工作起来。

在介绍Nand Flash的软件细节方面之前,先来介绍一下Nand Flash的两个相关的规范:ONFI和LBA。

ONFI规范,即Open Nand Flash Interface specification。

ONFI是Intel主导的,其他一些厂家(Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等)参与制定的,统一了Nand Flash的操作接口。

所谓操作接口,就是那些对Nand Flash操作的命令等内容。

而所谓统一,意思是之前那些Nand Flash的操作命令等,都是各自为政,虽然大多数常见的Nand Flash的操作,比如page read的命令是0x00,0x30,page write的命令是0x80,0x10等,但是有些命令相关的内容,很特别且很重要的一个例子就是,每个厂家的Nand Flash的read id的命令,虽然都是0x90,但是读取出来的几个字节的含义,每个厂家定义的都不太一样。

因此,才有统一Nand Flash的操作接口这一说。

ONFI规范,官网可以下载的到:

http://onfi.org/specifications/

比如:

ONFI 2.2 Spec

http://onfi.org/wp-content/uploads/2009/02/ONFI%202_2%20Gold.pdf

ONFI规范中定义的Nand Flash的命令集合为:


可以看到,其中常见的一些命令,比如

  1. page read(0x00,0x30)
  2. page write(0x80,0x10)
  3. block erase(0x60,0xD0)
  4. Reset(0xFF)

等等命令,都是和普通的Nand Flash的命令是一样的,而额外多出一些命令,比如Read Unique ID(0xED)等命令,是之前某些Nand Flash命令所不具有的。

如此,定义了Nand Flash的操作的命令的集合以及发送对应命令所遵循的时序等内容。

LBA Nand Flash,Logical Block Address,逻辑块寻址的Nand Flash,是Nand Flash大厂之一的Toshiba,自己独立设计出来的新一代的Nand Flash的规范。

之所以叫做逻辑块寻址,是相对于之前常见的,普通的Nand Flash的物理块的寻址来说的。常见的Nand Flash,如果要读取和写入数据,所用的对应的地址是对应的:block地址+block内的Page地址+Page内的偏移量 = 绝对的物理地址,

此物理块寻址,相对来说有个缺点,那就是,由于之前提到的Nand Flash会出现使用过程中出现坏块,所以,遇到这样的坏块,首先坏块管理要去将此坏块标记,然后将坏块的数据拷贝到另一个好的block中,再继续访问新的block。

而且数据读写过程中,还要有对应的ECC校验,很多情况下,也都是软件来实现这部分的工作,即使是硬件的ECC校验,也要写少量的软件,去操作对应寄存器,读取ECC校验的结果,当然别忘了,还有对应的负载平衡等工作。

如此的这类的坏块管理工作,对于软件来说,很是繁重,而且整个系统实现起来也不是很容易,所以,才催生了一个想法,是否可以把ECC校验,负载平衡,坏块管理,全部都放到硬件实现上,而对于软件来说,我都不关心,只关心有多少个Block供我使用,用于数据读写。

针对于此需求,Toshiba推出了LBA逻辑块寻址的Nand Flash,在Nand Flash存储芯片之外,加了对应一个硬件控制权Controller,实现了上述的坏块管理,ECC校验,负载平衡等工作,这样使得人家想要用你LBA的Nand Flash的人,去开发对应的软件来驱动LBA Nand Flash工作,相对要做的事情,就少了很多,相对来说就是减轻了软件系统集成方面的工作,提高了开发效率,缩短了产品上市周期。

LBA Nand,最早放出对应的样片(sample)是在2006年8月。

网上找到一个LBA Nand Flash的简介:

http://www.toshiba-components.com/prpdf/5678E.pdf

现早已经量产,偶在之前开发过程中,就用过其某款LBA的Nand Flash。

目前网上还找不到免费的LBA的规范。除非你搞开发,和Toshiba签订NDA协议后,才可以拿到对应的specification。

关于Toshiba LBA Nand规范,在此多说一点(参考附录中:lba-core.c):

LBA Nand分为PNP,VFP和MDP三种分区:

  1. PNP主要用于存放Uboot等启动代码
  2. VFP主要用于存放uImage等内核代码
  3. MDP主要用于存放用户的数据,以及rootfs等内容

MTD,是Linux的存储设备中的一个子系统。其设计此系统的目的是,对于内存类的设备,提供一个抽象层,一个接口,使得对于硬件驱动设计者来说,可以尽量少的去关心存储格式,比如FTL,FFS2等,而只需要去提供最简单的底层硬件设备的读/写/擦除函数就可以了。而对于数据对于上层使用者来说是如何表示的,硬件驱动设计者可以不关心,而MTD存储设备子系统都帮你做好了。

对于MTD子系统的好处,简单解释就是,他帮助你实现了,很多对于以前或者其他系统来说,本来也是你驱动设计者要去实现的很多功能。换句话说,有了MTD,使得你设计Nand Flash的驱动,所要做的事情,要少很多很多,因为大部分工作,都由MTD帮你做好了。

当然,这个好处的一个“副作用”就是,使得我们不了解的人去理解整个Linux驱动架构,以及MTD,变得更加复杂。但是,总的说,觉得是利远远大于弊,否则,就不仅需要你理解,而且还是做更多的工作,实现更多的功能了。

此外,还有一个重要的原因,那就是,前面提到的Nand Flash和普通硬盘等设备的特殊性:

有限的通过出复用来实现输入输出命令和地址/数据等的IO接口,最小单位是页而不是常见的bit,写前需擦除等,导致了这类设备,不能像平常对待硬盘等操作一样去操作,只能采取一些特殊方法,这就诞生了MTD设备的统一抽象层。

MTD,将Nand Flash,nor flash和其他类型的flash等设备,统一抽象成MTD设备来管理,根据这些设备的特点,上层实现了常见的操作函数封装,底层具体的内部实现,就需要驱动设计者自己来实现了。具体的内部硬件设备的读/写/擦除函数,那就是你必须实现的了。


多说一句,关于MTD更多的内容,感兴趣的,去附录中的MTD主页去看。

关于mtd设备驱动,感兴趣的可以去参考附录中MTD设备的文章,该文章是比较详细地介绍了整个MTD框架和流程,方便大家理解整个mtd框架和Nand Flash驱动。

关于nand flash,由于各个厂家的read id读出的内容的定义,都不同,导致,对于读出的id,分别要用不同的解析方法,下面这段代码,是我之前写的,本来打算自己写信去推荐到Linux MTD内核源码的,不过后来由于没搞懂具体申请流程,就放弃了。不过,后来,看到Linux的MTD部分更新了,加了和下面类似的做法。

此处只是为了记录下来,也算给感兴趣的人一个参考吧。

 

文件:\linux-2.6.28.4\drivers\mtd\nand\nand_base.c

 

/*
 * Get the flash and manufacturer id and lookup if the type is supported
 */
static struct nand_flash_dev *nand_get_flash_type(struct mtd_info *mtd,
                          struct nand_chip *chip,
                          int busw, int *maf_id)
{
... ...
    chip->chipsize = (uint64_t)type->chipsize << 20;

    /* 针对不同的MLC和SLC的nand flash,添加了不同的解析其ID的方法 */
    /* Newer devices have all the information in additional id bytes */
    if (!type->pagesize) {
        int erase_bits, page_base, block_base, old_50nm, new_40nm;
        uint8_t id3rd, id4th, id5th, id6th, id7th;

        /* The 3rd id byte holds MLC / multichip data */
        chip->cellinfo = id3rd = chip->read_byte(mtd);
        /* The 4th id byte is the important one */
        id4th = chip->read_byte(mtd);
        id5th = chip->read_byte(mtd);
        id6th = chip->read_byte(mtd);
        id7th = chip->read_byte(mtd);
        /* printk(KERN_INFO " (ID:%02x %02x %02x %02x %02x %02x %02x) ",
            id1st, id2nd, id3rd, id4th, id5th, id6th, id7th); */

        if (nand_is_mlc(chip->cellinfo)) {
            /*
             * MLC:
             * 50nm has 5 bytes ID, further read ID will periodically output
             * 40nm has 6 bytes ID
             */

            /*
             * the 4th byte is not the same meaning for different manufature
            */
            if (NAND_MFR_SAMSUNG == *maf_id) {
                /* samsung MLC chip has several type ID meanings:
                (1)50nm serials, such as K9GAG08U0M
                (2)40nm serials, such as K9LBG08UXD
                */

                /* old 50nm chip will periodically output if read further ID */
                old_50nm = (id1st == id6th) && (id2nd == id7th);
                /* is 40nm or newer */
                new_40nm = id6th & 0x07;
                if ((!old_50nm) && new_40nm) {
                    /*
                     * Samsang
                     * follow algorithm accordding to datasheets of:
                     * K9LBG08UXD_1.3 (40nm), 
                     * ID(hex): EC D7 D5 29 38 41
                     * this algorithm is suitable for new chip than 50nm
                     * such as K9GAG08u0D, 
                     * ID(hex): EC D5 94 29 B4 41
                     */

                    int bit236;

                    /* Calc pagesize, bit0,bit1: page size */
                    page_base = (1 << 11);  /* 2KB */
                    mtd->writesize = page_base * (1 << (id4th & BIT01));
                    block_base = (1 << 17); /* 128 KB */
                    /* Calc block size, bit4,bit5,bit7: block size */
                    erase_bits = (id4th >> 4) & BIT01; /* get bit4,bit5 */
                    erase_bits |= (id4th >> 5) & BIT(2); /* get bit7 and combine them */
                    mtd->erasesize = block_base * (1 << erase_bits);
                    /* Calc oobsize, bit2,bit3,bit6: oob size */
                    bit236 = (id4th >> 2) & BIT01; /* get bit2,bit3 */
                    bit236 |= (id4th >> 4) & BIT(2); /* get bit6 and combine them */
                    switch (bit236) {
                    case 0x01:
                        mtd->oobsize = 128;
                        break;
                    case 0x02:
                        mtd->oobsize = 218;
                        break;
                    default:
                        /* others reserved */
                        break;
                    }
                }
                else {
                    /*
                     * Samsang
                     * follow algorithm accordding to datasheets of:
                     * K9GAG08U0M (50nm)
                     * this algorithm is suitable for old 50nm chip
                     */

                    goto slc_algorithm;
                }
            }
            else if (NAND_MFR_TOSHIBA == *maf_id) {
                /*
                 * Toshiba
                 * follow algorithm guess from ID of TC58NVG3D1DTG00:
                 * Toshiba MLC TC58NVG3D1DTG00 1GB 8bit 1chip
                 * 4K+218 512K+27K 3.3V, (ID:98 D3 94 BA 64 13 42)
                 */
                int bit23;

                /* Calc pagesize, bit0,bit1: page size */
                page_base = (1 << 10);  /* 1KB */
                mtd->writesize = page_base * (1 << (id4th & BIT01));
                block_base = (1 << 16); /* 64 KB */
                /* Calc block size, bit4,bit5: block size */
                erase_bits = (id4th >> 4) & BIT01; /* get bit4,bit5 */
                mtd->erasesize = block_base * (1 << erase_bits);
                /* Calc oobsize, use spare/redundant area bit */
                bit23 = (id4th >> 2) & BIT01; /* get bit2,bit3 */
                switch (bit23) {
                case 0x01:
                    mtd->oobsize = 128;
                    break;
                case 0x02:
                    mtd->oobsize = 218;
                    break;
                default:
                    /* others reserved */
                    break;
                }
                /* Get buswidth information: x8 or x16 */
                busw = ((id4th >> 6) & BIT(0)) ? NAND_BUSWIDTH_16 : 0;
            }
            else if (NAND_MFR_MICRON == *maf_id) {
                /*
                 * Micron
                 * follow algorithm accordding to datasheets of:
                 * 29F32G08CBAAA
                 */
                int spare_area_size_bit;

                /* Calc pagesize, bit0,bit1: page size */
                page_base = (1 << 10);  /* 1KB */
                mtd->writesize = page_base * (1 << (id4th & 0x03));
                block_base = (1 << 16); /* 64 KB */
                /* Calc block size, bit4,bit5: block size */
                erase_bits = (id4th >> 4) & BIT01; /* get bit4,bit5 */
                mtd->erasesize = block_base * (1 << erase_bits);
                /* Calc oobsize, use spare/redundant area bit */
                spare_area_size_bit = (id4th >> 2) & BIT(0);
                if (spare_area_size_bit) /* special oob */
                    mtd->oobsize = 218;
                else /* normal */
                    mtd->oobsize = mtd->writesize / NAND_PAGE_OOB_RATIO;
                /* Get buswidth information: x8 or x16 */
                busw = ((id4th >> 6) & BIT(0)) ? NAND_BUSWIDTH_16 : 0;
            }
            else {
                /*
                 * Others
                 * FIXME: update follow algrithm,
                 * according to different manufacture‘s chip‘s datasheet
                 */

                goto slc_algorithm;
            }       
        }
        else {
            /*
             * SLC, only has 4 bytes ID, further read will output periodically, such as:
             * Hynix : HY27UG084G2M, only has 4 byte ID,
             * following read ID is periodically same as the 1st ~ 4th byte,
             * for HY27UG084G2M is : 0xAD 0xDC 0x80 0x15 0xAD 0xDC 0x80 0x15 ..... 
            */
slc_algorithm:
            /* Calc pagesize, bit0,bit1: page size */
            page_base = (1 << 10);  /* 1KB */
            mtd->writesize = page_base * (1 << (id4th & BIT01));
            block_base = (1 << 16); /* 64 KB */
            /* Calc block size, bit4,bit5: block size */
            erase_bits = (id4th >> 4) & BIT01; /* get bit4,bit5 */
            mtd->erasesize = block_base * (1 << erase_bits);
            /* Calc oobsize, use fixed ratio */
            mtd->oobsize = mtd->writesize / NAND_PAGE_OOB_RATIO;
            /* Get buswidth information: x8 or x16 */
            busw = ((id4th >> 6) & BIT(0)) ? NAND_BUSWIDTH_16 : 0;
        }
    } else {
        /*
         * Old devices have chip data hardcoded in the device id table
         */
        mtd->erasesize = type->erasesize;
        mtd->writesize = type->pagesize;
        mtd->oobsize = mtd->writesize / NAND_PAGE_OOB_RATIO;
        busw = type->options & NAND_BUSWIDTH_16;
    }
    
    /*
    * 以上内容,主要是更加不同厂家的nand flash的datasheet,一点点总结出来的算法。
    * 最新的Linux的MTD部分,已经添加了类似如上部分的代码。此处贴出来,仅供参考。
    */
    
    /*
     * Check, if buswidth is correct. Hardware drivers should set
     * chip correct !
     */
    if (busw != (chip->options & NAND_BUSWIDTH_16)) {
        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, mtd->name);
        printk(KERN_WARNING "NAND bus width %d instead %d bit\n",
               (chip->options & NAND_BUSWIDTH_16) ? 16 : 8,
               busw ? 16 : 8);
        return ERR_PTR(-EINVAL);
    }
... ...
}

            

 

下面这部分主要介绍一下,关于硬件的设计和规范,是如何映射到具体的软件实现的,看了这部分内容之后,你对如何根据硬件的规范去用软件代码实现对应的功能,就有了大概的了解了,然后去实现对应的某硬件的驱动,就有了大概的脉络了。

关于硬件部分的细节,前面其实已经介绍过了,但是为了方便说明,此处还是以读操作为例去讲解硬件到软件是如何映射的。

再次贴出上面的那个图:


对于上面的从1到6,每个阶段所表示的含义,再简单解释一下:

上面的是内容,说的是硬件是如何设计的,而这硬件的设计,即硬件的逻辑时序是如何规定的,对应的软件实现,也就要如何实现。不过可以看出的是,其中很多步骤,比如步骤1和步骤4,那都是固定的,而且,即使其中的步骤2和步骤3,即使是不同厂家和不同的Nand Flash芯片,除了要写入的列地址和行地址可能不同之外,也都是逻辑一样的,同样地,步骤5和6,也都是一样的,唯一不同的,是每家不同的Nand Flash控制器不同,所以具体到步骤6的时候,去读出数据的方式不同,所以,那一部分肯定是你要实现Nand Flash驱动的时候要自己实现的,而对应的其他几个公有的步骤呢,就有了Linux的MTD层帮你实现好了,所以,下面就来介绍一下,关于读取一个Nand Flash的页Page,Linux的MTD层,是如何具体的帮你实现的:

关于Nand Flash的读操作,即读取一页的数据,这样的读数据的操作,很明显,是从上层文件系统传递过来的,其细节我们在此忽略,但是要知道,上层读取数据的请求,传递到了MTD这一层,其入口是哪个函数,然后我们才能继续往下面分析细节。

关于下面所要的介绍的代码,如果没有明确指出,都是位于此文件:

代码位置:\drivers\mtd\nand\nand_base.c

MTD读取数据的入口是nand_read,然后调用nand_do_read_ops,此函数主体如下:

对于上述1中的函数cmdfunc,一般来说可以不用自己的驱动中实现,而直接使用MTD层提供的已有的函数,nand_command_lp,其细节如下:

static void nand_command_lp(struct mtd_info *mtd, unsigned int command, int column, int page_addr)
{
    ......
    / * Command latch cycle */
    chip->cmd_ctrl(mtd, command & 0xff, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);1

    if (column ! = - 1 | | page_addr ! = - 1) {
        int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
        / * Serially input address */
        if (column ! = - 1) {
            / * Adjust columns for 16 bit buswidth */
            if (chip->options & NAND_BUSWIDTH_16)
            column >>= 1;
            chip->cmd_ctrl(mtd, column, ctrl);2
            ctrl &= ~NAND_CTRL_CHANGE;
            chip->cmd_ctrl(mtd, column >> 8, ctrl);
        }
        if (page_addr ! = - 1) {
            chip->cmd_ctrl(mtd, page_addr, ctrl);3
            chip->cmd_ctrl(mtd, page_addr >> 8, NAND_NCE | NAND_ALE);
            / * One more address cycle for devices > 128MiB */
            if (chip->chipsize > (128 << 20))
            chip->cmd_ctrl(mtd, page_addr >> 16, NAND_NCE | NAND_ALE);
        }
    }
    chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
    / *
    * program and erase have their own busy handlers
    * status, sequential in, and deplete1 need no delay
    */
    switch (command) {
        ......
        case NAND_CMD_READ0:
            chip->cmd_ctrl(mtd, NAND_CMD_READSTART, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);4
            chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
            / * This applies to read commands */
        default:
        ......
    }

    / * Apply this short delay always to ensure that we do wait tWB in
    * any case on any machine. */
    /* */
    ndelay(100);5

    nand_wait_ready(mtd);6
}
        

1

此处就是就是发送读命令的第一个周期1st Cycle的命令,即0x00,对应着上述时序图中的1

2

接下来是发送两个column,列地址,对应着上述时序图中的2

3

然后发送三个row行地址,对应着上述时序图中的3

4

接下来发送读命令的第二个周期2nd Cycle的命令,即0x30,对应着上述时序图中的4

5

此处是对应着上述时序图中4的tWB的等待时间

6

接下来就是要等待一定的时间,使得Nand Flash硬件上准备好数据,以供之后读取,即对应着时序图中的5

对于之前的2的函数read_page,一般来说也可以不用自己的驱动中实现,而直接使用MTD层提供的已有的函数,nand_read_page_hwecc,该函数所要实现的功能,正是上面余下没介绍的6,即一点点的读出我们要的数据:

上面的read_buf,就是真正的去读取数据的函数了,由于不同的Nand Flash controller控制器所实现的方式不同,所以这个函数必须在你的Nand Flash驱动中实现,即MTD层,能帮我们实现的都实现了,不能实现的,那肯定要你的驱动自己实现。

对于我们这里的s3c2410的例子来说,就是s3c2410_nand_read_buf

文件位置:\drivers\mtd\nand\s3c2410.c

可以看出,此处的实现相当地的简单,就是读取对应的IO的地址,然后就可以把数据读出来就可以了。

不过,要注意的是,并不是所有的驱动都是这么简单,具体情况则是不同的Nand Flash控制器对应不同实现方法。

至此,关于整个的Nand Flash的读取一页的数据的操作,是如何将硬件的逻辑时序图,映射到对应的软件的实现的,就已经介绍完了。而看懂了这个过程,你才会更加明白,原来MTD层,已经帮助我们实现了很多很多通用的操作所对应的软件部分,而只需要我们实现剩下那些和具体硬件相关的操作的函数,就可以了,可以说大大减轻了驱动开发者的工作量。

因为,如果没了MTD层,那么上面那么多的函数,几乎都要我们自己实现,单单是代码量,就很庞大,而且再加上写完代码后的驱动测试功能是否正常,使得整个驱动开发,变得难的多得多。

在介绍具体如何写Nand Flash驱动之前,我们先要了解,大概的整个系统,和Nand Flash相关的部分的驱动工作流程,这样,对于后面的驱动实现,才能更加清楚机制,才更容易实现,否则就是,即使写完了代码,也还是没搞懂系统是如何工作的了。

让我们以最常见的,Linux内核中已经有的三星的Nand Flash驱动,来解释Nand Flash驱动具体流程和原理。

此处是参考2.6.29版本的Linux源码中的\drivers\mtd\nand\s3c2410.c,以2410为例。

在Nand Flash驱动加载后,第一步,就是去调用对应的init函数,s3c2410_nand_init,去将在Nand Flash驱动注册到Linux驱动框架中。

驱动本身,真正开始,是从probe函数,s3c2410_nand_probe->s3c24xx_nand_probe,

在probe过程中,去用clk_enable打开Nand Flash控制器的clock时钟,用request_mem_region去申请驱动所需要的一些内存等相关资源。然后,在s3c2410_nand_inithw中,去初始化硬件相关的部分,主要是关于时钟频率的计算,以及启用Nand Flash控制器,使得硬件初始化好了,后面才能正常工作。

需要多解释一下的,是这部分代码:

等所有的参数都计算好了,函数都挂载完毕,系统就可以正常工作了。

上层访问你的nand falsh中的数据的时候,通过MTD层,一层层调用,最后调用到你所实现的那些底层访问硬件数据/缓存的函数中。

摘要

关于上面提到的,在nand_scan_tail的时候,系统会根据你的驱动,如果没有实现一些函数的话,那么就用系统默认的。如果实现了自己的函数,就用你的。

估计很多人就会问了,那么到底我要实现哪些函数呢,而又有哪些是可以不实现,用系统默认的就可以了呢。

此问题的,就是我们下面要介绍的,也就是,你要实现的,你的驱动最少要做哪些工作,才能使整个Nand Flash工作起来。

 

 

 

其实,要了解,关于驱动框架部分,你所要做的事情的话,只要看看三星的整个Nand Flash驱动中的这个结构体,就差不多了:

而对于底层硬件操作的有些函数,总体上说,都可以在上面提到的s3c2410_nand_init_chip中找到:

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->write_buf    = s3c2410_nand_write_buf;1
	chip->read_buf     = s3c2410_nand_read_buf;
	chip->select_chip  = s3c2410_nand_select_chip;2
	chip->chip_delay   = 50;
	chip->priv	   = nmtd;
	chip->options	   = 0;
	chip->controller   = &info->controller;

	switch (info->cpu_type) {
	case TYPE_S3C2410:3
		chip->IO_ADDR_W = regs + S3C2410_NFDATA;
		info->sel_reg   = regs + S3C2410_NFCONF;
		info->sel_bit	= S3C2410_NFCONF_nFCE;
		chip->cmd_ctrl  = s3c2410_nand_hwcontrol;4
		chip->dev_ready = s3c2410_nand_devready;5
		break;
。。。。。。
  	}

	chip->IO_ADDR_R = chip->IO_ADDR_W;

	nmtd->info	   = info;
	nmtd->mtd.priv	   = chip;
	nmtd->mtd.owner    = THIS_MODULE;
	nmtd->set	   = set;

	if (hardware_ecc) {
		chip->ecc.calculate = s3c2410_nand_calculate_ecc;6
		chip->ecc.correct   = s3c2410_nand_correct_data;7
		chip->ecc.mode	    = NAND_ECC_HW;8

		switch (info->cpu_type) {
		case TYPE_S3C2410:
			chip->ecc.hwctl	    = s3c2410_nand_enable_hwecc;9
			chip->ecc.calculate = s3c2410_nand_calculate_ecc;
			break;
......
		}
	} else {
		chip->ecc.mode	    = NAND_ECC_SOFT;
	}

	if (set->ecc_layout != NULL)
		chip->ecc.layout = set->ecc_layout;

	if (set->disable_ecc)
		chip->ecc.mode	= NAND_ECC_NONE;
}
            

1

s3c2410_nand_write_buf 和 s3c2410_nand_read_buf:这是两个最基本的操作函数,其功能,就是往你的Nand Flash的控制器中的FIFO读写数据。一般情况下,是MTD上层的操作,比如要读取一页的数据,那么在发送完相关的读命令和等待时间之后,就会调用到你底层的read_buf,去Nand Flash的FIFO中,一点点把我们要的数据,读取出来,放到我们制定的内存的缓存中去。写操作也是类似,将我们内存中的数据,写到Nand Flash的FIFO中去。

2

s3c2410_nand_select_chip : 实现Nand Flash的片选。

3

Nand Flash控制器中,一般都有对应的数据寄存器,用于给你往里面写数据,表示将要读取或写入多少个字节(byte,u8)/字(word,u32) ,所以,此处,你要给出地址,以便后面的操作所使用

4

s3c2410_nand_hwcontrol:给底层发送命令或地址,或者设置具体操作的模式,都是通过此函数。

5

s3c2410_nand_devready:Nand Flash的一些操作,比如读一页数据,写入(编程)一页数据,擦除一个块,都是需要一定时间的,在命发送完成后,就是硬件开始忙着工作的时候了,而硬件什么时候完成这些操作,什么时候不忙了,变就绪了,就是通过这个函数去检查状态的。

一般具体实现都是去读硬件的一个状态寄存器,其中某一位是否是1,对应着是出于“就绪/不忙”还是“忙”的状态。这个寄存器,也就是我们前面分析时序图中的R/B#。

6

s3c2410_nand_calculate_ecc:如果是上面提到的硬件ECC的话,就不用我们用软件去实现校验算法了,而是直接去读取硬件产生的ECC数值就可以了。

7

s3c2410_nand_correct_data:当实际操作过程中,读取出来的数据所对应的硬件或软件计算出来的ECC,和从oob中读出来的ECC不一样的时候,就是说明数据有误了,就需要调用此函数去纠正错误。对于现在SLC常见的ECC算法来说,可以发现2位,纠正1位。如果错误大于1位,那么就无法纠正回来了。一般情况下,出错超过1位的,好像几率不大。至少我看到的不是很大。更复杂的情况和更加注重数据安全的情况下,一般是需要另外实现更高效和检错和纠错能力更强的ECC算法的。

8

此处,多数情况下,你所用的Nand Flash的控制器,都是支持硬件ECC的,所以,此处设置硬件ECC(HW_ECC) ,也是充分利用硬件的特性,而如果此处不用硬件去做的ECC的话,那么下面也会去设置成NAND_ECC_SOFT,系统会用默认的软件去做ECC校验,相比之下,比硬件ECC的效率就低很多,而你的Nand Flash的读写,也会相应地要慢不少

9

s3c2410_nand_enable_hwecc: 在硬件支持的前提下,前面设置了硬件ECC的话,要实现这个函数,用于每次在读写操作前,通过设置对应的硬件寄存器的某些位,使得启用硬件ECC,这样在读写操作完成后,就可以去读取硬件校验产生出来的ECC数值了。

当然,除了这些你必须实现的函数之外,在你更加熟悉整个框架之后,你可以根据你自己的Nand Flash的特点,去实现其他一些原先用系统默认但是效率不高的函数,而用自己的更高效率的函数替代他们,以提升你的Nand Flash的整体性能和效率。

【详解】如何编写Linux下Nand Flash驱动,布布扣,bubuko.com

【详解】如何编写Linux下Nand Flash驱动

标签:des   style   blog   class   code   tar   

发布此文章仅为传递网友分享,不代表本站观点,若侵权请联系我们删除,本站将不对此承担任何责任。
Copyright ©2018  IT大杂烩  版权所有  京ICP备11030978号-1 网站地图