【详解二】Nand Flash 编写驱动之前要了解的知识

1.2.15.1. 页编程(Page Program)注意事项

Nand flash的写操作叫做编程Program,编程,一般情况下,是以页为单位的。

有的Nand Flash,比如K9K8G08U0A,支持部分页编程(Partial Page Program),但是有一些限制:在同一个页内的,连续的部分页的编程,不能超过4次。

一般情况下,都是以页为单位进行编程操作的,很少使用到部分页编程。

关于这个部分页编程,本来是一个页的写操作,却用两个命令或更多的命令去实现,看起来是操作多余,效率不高,但是实际上,有其特殊考虑:

至少对于块擦除来说,开始的命令0x60是擦除设置命令(erase setup comman),然后传入要擦除的块地址,然后再传入擦除确认命令(erase confirm command)0xD0,以开始擦除的操作。

这种完成单个操作要分两步发送命令的设计,即先开始设置,再最后确认的命令方式,是为了避免由于外部由于无意的/未预料而产生的噪音,比如,由于某种噪音,而产生了0x60命令,此时,即使被Nand Flash误认为是擦除操作,但是没有之后的确认操作0xD0,Nand Flash就不会去擦除数据,这样使得数据更安全,不会由于噪音而误操作。

1.2.15.2. 读(Read)操作过程详解

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

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

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

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

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

1.2.15.2.1. 需要使用何种命令

首先,是要了解,对于读取数据,要用什么命令:

根据前面关于Nand Flash的命令集合介绍,我们知道,要读取数据,要用到Read命令,该命令需要2个周期,第一个周期发0x00,第二个周期发0x30。

1.2.15.2.2. 发送命令前的准备工作以及时序图各个信号的具体含义

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

[提示]提示

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

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

图 1.7. Nand Flash数据读取操作的时序图

Nand Flash数据读取操作的时序图

 

[注意]注意
此图来自三星的型号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忙的状态的。

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

1.2.15.2.3. 如何计算出我们要传入的行地址和列地址

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

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

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

物理地址

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

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

=0x36B204B8

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

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

图 1.8. Nand Flash的地址周期组成

Nand Flash的地址周期组成

 

结合图 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 ~ A01011 1000 = 0xB8
2nd周期A11~ A80100 = 0x04
3rd周期A19~A120010 0000 = 0x20
4th周期A27~A200110 1011 = 0x6B
5th周期A30~A2811 = 0x03
[注意]注意
图 1.7 “Nand Flash数据读取操作的时序图”中对应的,*L,意思是地电平,由于未用到那些位,datasheet中强制要求设为0,所以,才有上面的2nd周期中的高4位是0000.其他的A30之后的位也是类似原理,都是0。

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

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

0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000

1st周期A7 ~ A01011 1000 = 0xB8
2nd周期A10~ A8100 = 0x04
3rd周期A19~A12010 0000 0 = 0x40
4th周期A27~A20110 1011 0 = 0xD6
5th周期A30~A2811 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中,也是这样处理的:

static void nand_command_lp(struct mtd_info *mtd, unsigned int command,
                int column, int page_addr)
{
......
        /* Serially input address */
        if (column != -1) {
......
            chip->cmd_ctrl(mtd, column, ctrl); /* 发送Col Addr 1 */
            ctrl &= ~NAND_CTRL_CHANGE;
            chip->cmd_ctrl(mtd, column >> 8, ctrl); /* 发送Col Addr 2 */
        }
        if (page_addr != -1) {
            chip->cmd_ctrl(mtd, page_addr, ctrl); /* 发送Row Addr 1 */
            chip->cmd_ctrl(mtd, page_addr >> 8, /* 发送Row Addr 2 */
                       NAND_NCE | NAND_ALE); 
            /* One more address cycle for devices > 128MiB */
            if (chip->chipsize > (128 << 20))
                chip->cmd_ctrl(mtd, page_addr >> 16, /* 发送Row Addr 3 */
                           NAND_NCE | NAND_ALE); 
        }
}
                

1

column,即页内地址,多数情况下,都是0,即使不是0,也可以直接通过将传入的地址除于页地址所得的余数,就是列地址了。

2

page_addr即页号,也很简单,就是通过要访问的地址,除于页大小,即可得到。

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

分别传入两个列地址的:

列地址1=0xB8

列地址2=0x04

然后再传3个行地址的:

行号1=0x40

行号2=0xD6

行号3=0x06

这样硬件才能识别。

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

1.2.15.2.4. 读操作过程的解释

准备工作终于完了,下面就可以开始解释说明,对于读操作的,上面图中标出来的,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,根据里面的时序图去分析具体的操作过程,然后对照代码,会更加清楚具体是如何实现的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值