1.3.3. 读操作的硬件到软件的映射

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

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

再次贴出上面的那个图:

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

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


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

  1. 此阶段,是读命令第一个周期,发送的命令为0x00。
  2. 此阶段,依次发送列地址,关于这些行地址,列地址等是如何计算出来的,后面的内容会有详细解释。
  3. 此阶段是发送对应的行地址
  4. 此阶段是发送读命令第二周期2nd cycle所对应的命令,0x30
  5. 此阶段是等待时间,等待Nand Flash硬件上准备好对应的数据,以便后续读出。
  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,此函数主体如下:

static int nand_do_read_ops(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
{
    ............
    while(1) {
        ......
        chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page); 1
        ......
        ret = chip->ecc.read_page(mtd, chip, bufpoi, 2
        buf += bytes;
        ......
        readlen - = bytes;
        if (! readlen)
        break;
        / * For subsequent reads align to page boundary. */
        col = 0;
        / * Increment page address */
        realpage++;
        page = realpage & chip->pagemask;
        ......
        }
    ......
    }

    ............
    return mtd->ecc_stats.corrected - stats.corrected ? - EUCLEAN : 0;
}
        

1

要读取数据,肯定是要先发送对应的读页(read page)的命令

2

发送完命令,接着就可以去调用read_page函数读取对应的数据了

对于上述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,即一点点的读出我们要的数据:

static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int page)
{
    ......
    for (i = 0; eccsteps; eccsteps-- , i += eccbytes, p += eccsize) {
        chip->ecc.hwctl(mtd, NAND_ECC_READ);

        chip->read_buf(mtd, p, eccsize);1

        chip->ecc.calculate(mtd, p, &ecc_calc[i]);
    }
    ......
}
        

1

真正的数据读取,就是下面这个read_buf函数了

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

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

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

static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
{
    struct nand_chip *this = mtd->priv;
    readsb(this->IO_ADDR_R, buf, len);1
}
        

1

真正的地址去读取数据

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

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

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

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