基於MTD的NANDFLASH設備驅動底層實現原理分析

經過UBOOT初步的移植,Linux內核初步的移植,Linux內核總線設備模型的分析,等一系列

痛苦的折騰,目的就是想更好的來分析下NANDFLASH的驅動。。大概一共歷經了半個月

的時間,慢慢的對NANDFLASH驅動程序有感覺了。。。

一、MTD體系結構:Linux內核提供MTD子系統來建立FLASH針對Linux的統一、抽象接口。MTD將文件系統與底層的FLASH存儲器進行隔離。

      引入MTD後Linux系統中對FLASH的設備驅動分爲4層

   

設備節點:用戶在/dev目錄下使用mknod命令建立MTD字符設備節點(主設備號爲90),或者MTD塊設備節點(主設備號爲31),使用該設備節點即可訪問MTD設備。

MTD設備層:基於MTD原始設備層,系統將MTD設備可以定義爲MTD字符(在/mtd/mtdchar.c中實現,設備號90)和MTD塊設備(在/mtd/mtdblock.c中實現,設備號31)。

MTD原始設備層:MTD原始設備層由兩部分構成,一部分是MTD原始設備的通用代碼,另一部分是各個特定Flash的數據,如分區。

                主要構成的文件有:

                     drivers/mtd/mtdcore.c支持mtd字符設備

                     driver/mtd/mtdpart.c支持mtd塊設備


Flash硬件驅動層:Flash硬件驅動層負責對Flash硬件的讀、寫和擦除操作。MTD設備的Nor Flash芯片驅動位於drivers/mtd/chips/子目錄下,Nand Flash芯片的驅動則位於drivers/mtd/nand/子目錄下。

二、Linux內核中基於MTD的NANDFLASH驅動代碼佈局:

在Linux2.6.35內核中,MTD源代碼放在driver/mtd目錄中,該目錄中包含chips、devices、maps、nand、onenand、lpdrr、tests和ubi八個子目錄。
其中只有nand和onenand目錄中的代碼才與NAND驅動相關,不過nand目錄中的代碼比較通用,而onenand目錄中的代碼相對於nand中的代碼而言則簡化了很多,它是針對三星公司開發的另一類Flash芯片,即OneNAND Flash。
本文我們需要關注的代碼是linux-2.6.35/drivers/mtd/nand目錄中,在該目錄中我們關心的文件如下:
1、  nand_base.c:
定義了NAND驅動中對NAND芯片最基本的操作函數和操作流程,如擦除、讀寫page、讀寫oob等。當然這些函數都只是進行一些default的操作,若你的系統在對NAND操作時有一些特殊的動作,則需要在你自己的驅動代碼中進行定義,然後Replace這些default的函數。
2、  nand_bbt.c:
定義了NAND驅動中與壞塊管理有關的函數和結構體。
3、  nand_ids.c:
定義了兩個全局類型的結構體:struct nand_flash_dev nand_flash_ids[ ]和struct nand_manufacturers nand_manuf_ids[ ]。其中前者定義了一些NAND芯片的類型,後者定義了NAND芯片的幾個廠商。NAND芯片的ID至少包含兩項內容:廠商ID和廠商爲自己的NAND芯片定義的芯片ID。當NAND驅動被加載的時候,它會去讀取具體NAND芯片的ID,然後根據讀取的內容到上述定義的nand_manuf_ids[ ]和nand_flash_ids[ ]兩個結構體中去查找,以此判斷該NAND芯片是那個廠商的產品,以及該NAND芯片的類型。若查找不到,則NAND驅動就會加載失敗,因此在開發NAND驅動前必須事先將你的NAND芯片添加到這兩個結構體中去(其實這兩個結構體中已經定義了市場上絕大多數的NAND芯片,所以除非你的NAND芯片實在比較特殊,否則一般不需要額外添加)。值得一提的是,nand_flash_ids[ ]中有三項屬性比較重要,即pagesize、chipsize和erasesize,驅動就是依據這三項屬性來決定對NAND芯片進行擦除,讀寫等操作時的大小的。其中pagesize即NAND芯片的頁大小,一般爲256、512或2048;chipsize即NAND芯片的容量;erasesize即每次擦除操作的大小,通常就是NAND芯片的block大小。
4、  nand_ecc.c:
定義了NAND驅動中與softeware ECC有關的函數和結構體,若你的系統支持hardware ECC,且不需要software ECC,則該文件也不需理會。
上面這些內容我是Copy別人的我覺得寫得太好了,因爲一開始我真的很迷茫,在nand目錄下有那麼多的文件,到底哪個是值得我讀的.我真的不值得,讀了這個大神的博客後對NANDDLASH的驅動我不再是那麼的迷茫。

三、NANDFLASH的硬件特性

要想讀懂後面Linux系統中對NANDFLASH硬件驅動代碼,瞭解NANDFLASH的硬件特性這是再好不過的。

1、NANDFLASH的內部佈局

   

2、Nand Flash的物理存儲單元的陣列組織結構(以開發板上的K9F2G08爲例)

   

        K9F2G08的大小是256M

    a)block:"Block是Nand Flash的擦除操作的基本/最小單位",一片NANDFLASH(chip)由很多塊

(block)組成,塊的大小一般是 128KB, 256KB,512KB,此處是 128KB。。其他的小於 128KB 的,

比如 64KB稱之爲small block的Nand Flash。

    b)page:"page是讀寫操作的最小單位",每一個block裏面包又含了許多page(頁),每個頁的大小,

對於現在常見的Nand Flash多數是2KB,最新的Nand Flash的是4KB、8KB等,這類的頁大小大於

2KB的NandFlash,被稱作 big block的 Nand Flash,對應的發讀寫命令地址,一共 5個週期(cycle),

而老的 Nand Flash,頁大小是 256B,512B,,這類的 Nand Flash被稱作 small block的nandflash

地址週期只有4個。

    c)oob:每一個頁,對應還有一塊區域,叫做空閒區域(spare area)/冗餘區域(redundant area)而
Linux 系統中,一般叫做 OOB(Out Of Band),這個區域,是最初基於Nand Flash的硬件特
性:數據在讀寫時候相對容易錯誤,所以爲了保證數據的正確性,必須要有對應的檢測和糾
錯機制,此機制被叫做 EDC(Error Detection Code)/ECC(Error Code Correction, 或者  Error
Checking and Correcting),所以設計了多餘的區域,用於放置數據的校驗值。
Oob 的讀寫操作,一般是隨着頁的操作一起完成的,即讀寫頁的時候,對應地就讀寫了 oob。 
關於 oob具體用途,總結起來有:
   1、 標記是否是壞快
   2、存儲ECC數據
   3、存儲一些和文件系統相關的數據。如 jffs2 就會用到這些空間存儲一些特定信息,
   4、而yaffs2 文件系統,會在 oob中,存放很多和自己文件系統相關的信息。

3、K9F2G08的引腳定義

   

      IO7~IO0:用於輸入地址/數據/命令,輸出數據

    CLE:命令鎖存使能位,在發送命令之前要先將模式寄存器中設置CLE使能(高電平有效)。

    ALE:地址鎖存使能位,在發送地址之前,要先將模式寄存器中設置ALE使能(高電平有效)。

    CE:(nFCE)芯片的片選信號,操作nandflash前應該拉低該位使之選中該芯片。

    RE:(nFRE)讀使能,低電平有效,讀之前使CE有效。

    WE:(nFWE)寫使能,低電平有效,寫之前必須使WE有效。

    WP:寫保護低電平有效

    R/B:(R/nB)Ready/Busy Output,就緒/忙,主要用於在發送完編程/擦除命令後,檢測這些操作是
否完成,忙,表示編程/擦除操作仍在進行中,就緒表示操作完成。(其中就緒:高電平,忙:低電平)。

四、常見的NANDFLASH的操作

      1、要實現對 Nand Flash 的操作,比如讀取一頁的數據,寫入一頁的數據等,都要發送對應的命令,而且要符合硬件的規定,如圖:

   

比如說要實現讀一頁的數據,就要發送Read命令,而且分兩個週期發送,即分兩次發送對應的命令,第一次是 0x00h,第二次是 0x30h,而兩次命令中間,需要發送對應的你所要讀取的頁的地址,對應地,其他常見的一些操作,比如寫一個頁的數據(Page Program),就是先發送 0x80h,然後發送要寫入的地址,再發送0x10h。

    2、讀(Read)nandflash操作過程分析

   

    1)紅色豎線穿過的第一行,是 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忙的狀態的。 其他的時序的就類似的理解。

3、計算我們要讀取或者寫入的行地址和例地址

     以mini2440開發板上的K9F2G08爲例,此Nand Flash,一共有 2048 個塊,每個塊內有 64 頁,每個頁是 2K+64 Bytes。

     假設,我們要訪問其中的第 1000個塊中的第 25 頁中的 1208字節處的地址,此時,我們就要先把具體的地址算出來:

    物理地址
    =塊大小*塊號 + 頁大小*頁號 + 頁內地址

   

    從上圖可以看出,該FLASH的地址週期一共有5個,2個列地址(Column)週期,3個行地址(Row)週期

    a)對應的列地址就是頁內地址,該flash一個頁的大小是2K即2048個字節,所以它的地址範圍是0~2047,對應的上圖的列地址A0-A10就是頁內地址。你可能會發現多出了一個A11,從A0-A11,這樣一共就有了12位,那它的地址範圍就是0~2^12,即0~4096了,實際上,由於我們訪問頁內地址,可能會訪問到 oob 的位置,即 2048-2111 這 64 個字節的範圍內,所以,此處實際上只用到了 2048~2111,用於表示頁內的 oob 區域,其大小是 64字節。 

    b)對應地,A12~A28,稱作頁號,頁的號碼,可以定位到具體是哪一個頁,該FLASH一共是64頁一共需要6位即A12~A17,而其中A18~A28表示對應的塊號,即屬於哪個塊。

這裏有一個很重要的地方就是我們要傳入的地址的每一位,就是對應着上表中的 A0 到 A28 ,實際上上表中的 A11是比較特殊的,只有當我們訪問頁內地址處於 oob的位置,即屬於 2048~2111 的時候,A11纔會其效果,纔會用 A0-A11用來表示對應的某個屬於 2048~2111 的某個值,屬於 oob 的某個位置,而我們此處的頁內地址爲 1208,還沒有超過 2047 呢,所以 A11肯定是 0

然後我們再來算上面我們要訪問的地址:第 1000個塊中的第 25 頁中的 1208字節處的地址

第 1000個塊中的第 25 頁中的 1208字節處的地址它對應着的頁內地址爲:

頁內地址 =1208Bytes

              =0x4B8

頁      號 =塊數*每塊多少頁 + 塊內的頁號

              =1000*64 + 25

              =0xFA19

也就是我們要訪問0xFA19頁內的0x4B8地址,再把這個地址轉換成列和行地址

A0~A10是用來表示頁內地址的所以把0x4B8拆分成兩列

列地址1=0xB8,列地址2=0x04;

再把頁號0xFA19拆分成3行

行地址1=0x19,行地址2=0xFA,行地址3=0x0;

    對應的看看linux2.6.35/driver/mtd/nand/nand_base.c中地址的發送

   

上面的column即對應着頁內地址,通常情況是0,如果不是0則通過傳入進來的地址除於頁地址就可得到相應的列地址了。而 page_addr 即頁號,就是通過要訪問的地址,除於頁大小,即可得到。

對於其他操作還正在研究中。。。。。。雖然說上面這些東東大部分都是來自別人的東西,但是我相信現在它已經變成我自己的東西了。。我不記得那個大帥的博客了,因爲我是直接把它的博客給保存到本地了。。

尷尬的說:我突然發現在寫這些關於NAND驅動的文章的時候,原來我一直是在改寫別人的博客。。。。。其實這並不要緊的,我也覺得這不僅僅是一種比較好的學習方法了,爲什麼呢,因爲當我在看他的博客的時候,我明白了一點,然後當我自己要寫的時候。。對這個東東又進一步瞭解一點了。。呵呵Copy也分檔次了

五、硬件時序到軟件代碼的演變過程對nand_base.c部分代碼的分析

該文件位於</linux2.6.35/dricer/mtd/nand/nand_base.c>

還是把那個讀NAND的硬件時序圖給貼上,如下圖:

   

①:此階段,是讀命令第一個週期,發送的命令爲0x00。
②:此階段,依次發送列地址,關於這些行地址,列地址等是如何計算出來的,後面的內容
會有詳細解釋。
③:此階段是發送對應的行地址
④:此階段是發送讀命令第二週期 2nd cycle所對應的命令,0x30
⑤:此階段是等待時間,等待 Nand Flash硬件上準備好對應的數據,以便後續讀出。
⑥:此階段,就是一點點地把所需要的數據讀出來。

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) {
           /******省略****/

          .。。。。。。。。。。。。。。。

            if (likely(sndcmd)) {/*#define NAND_CMD_READ0 0*/

                /*1)***讀取數據前肯定要先發送對應的讀頁命令******/

                chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);
                sndcmd = 0;
            }

            /* Now read the page into the buffer */
            if (unlikely(ops->mode == MTD_OOB_RAW))
                ret = chip->ecc.read_page_raw(mtd, chip,
                                  bufpoi, page);
            else if (!aligned && NAND_SUBPAGE_READ(chip) && !oob)
                ret = chip->ecc.read_subpage(mtd, chip, col, bytes, bufpoi);
            else

             /******執行到這裏read_page函數讀取對應的數據了******/

                ret = chip->ecc.read_page(mtd, chip, bufpoi,
                              page);
            if (ret < 0)
                break;

            /* Transfer not aligned data */
            if (!aligned) {
                if (!NAND_SUBPAGE_READ(chip) && !oob)
                    chip->pagebuf = realpage;
                memcpy(buf, chip->buffers->databuf + col, bytes);
            }

            buf += bytes;
          。。。。。。。。。。。。。。。。。。

    if (mtd->ecc_stats.failed - stats.failed)
        return -EBADMSG;

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

上面這些代碼都不需要我們去實現的,使用MTD層的自定義代碼就行。。。

nand_command_lp的分析

static void nand_command_lp(struct mtd_info *mtd, unsigned int command,
                int column, int page_addr)
{
    register struct nand_chip *chip = mtd->priv;

    /* Emulate NAND_CMD_READOOB */
    if (command == NAND_CMD_READOOB) {
        column += mtd->writesize;
        command = NAND_CMD_READ0;
    }

    /* Command latch cycle */

   /* 此處就是就是發送讀命令的第一個週期1st Cycle的命令,即0x00,對應着上述步驟中的① */

    chip->cmd_ctrl(mtd, command & 0xff,
               NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);

    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;

          /* 發送兩個column列地址,對應着上述步驟中的② */

            chip->cmd_ctrl(mtd, column, ctrl);/*發送列地址1*/
            ctrl &= ~NAND_CTRL_CHANGE;
            chip->cmd_ctrl(mtd, column >> 8, ctrl);/*發送列地址2*/
        }
        if (page_addr != -1) {

          /* 接下來是發送三個Row,行地址,對應着上述步驟中的② */

            chip->cmd_ctrl(mtd, page_addr, ctrl);/*發送行地址1*/
            chip->cmd_ctrl(mtd, page_addr >> 8,/*發送行地址2*/
                       NAND_NCE | NAND_ALE);
            /* One more address cycle for devices > 128MiB */
            if (chip->chipsize > (128 << 20))
                chip->cmd_ctrl(mtd, page_addr >> 16,/*發送行地址3*/
                           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) {
。。。。。。。。。。。。。
        return;
   /***復位**/
    case NAND_CMD_RESET:
        if (chip->dev_ready)
            break;
        udelay(chip->chip_delay);
        chip->cmd_ctrl(mtd, NAND_CMD_STATUS,
                   NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
        chip->cmd_ctrl(mtd, NAND_CMD_NONE,
                   NAND_NCE | NAND_CTRL_CHANGE);
        while (!(chip->read_byte(mtd) & NAND_STATUS_READY)) ;
        return;
    /*讀忙信號*/
    case NAND_CMD_RNDOUT:
        /* No ready / busy check necessary */
        chip->cmd_ctrl(mtd, NAND_CMD_RNDOUTSTART,
                   NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
        chip->cmd_ctrl(mtd, NAND_CMD_NONE,
                   NAND_NCE | NAND_CTRL_CHANGE);
        return;
/* 接下來發送讀命令的第二個週期2nd Cycle的命令,即0x30,對應着上述步驟
中的④ */
    case NAND_CMD_READ0:
        chip->cmd_ctrl(mtd, NAND_CMD_READSTART,
                   NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
        chip->cmd_ctrl(mtd, NAND_CMD_NONE,
                   NAND_NCE | NAND_CTRL_CHANGE);

        /* This applies to read commands */
    default:
        /*
         * If we don't have access to the busy pin, we apply the given
         * command delay
         */
        if (!chip->dev_ready) {
            udelay(chip->chip_delay);
            return;
        }
    }

    /* Apply this short delay always to ensure that we do wait tWB in
     * any case on any machine. */

/* 此處是對應着④中的tWB的等待時間*/

    ndelay(100);
/* 接下來就是要等待一定的時間,使得Nand Flash硬件上準備好數據,以供你之
後讀取,即對應着步驟⑤ */
    nand_wait_ready(mtd);
}

還有一個步驟沒有實現那就是步驟了一點一點的把數據讀出來

 nand_read_page_hwecc分析

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);

         /**這個最重要了這纔是真正的從NAND的緩衝區中把數據給讀出來****/

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

 

  。。。。。。。。。。
    return 0;
}

上面的 read_buf,就是真正的去讀取數據的函數了,由於不同的Nand Flash controller 控制器所實現的方式不同,所以這個函數必須在你的 Nand Flash驅動中實現,即MTD 層,能幫我們實現的都實現了,不能實現的,那肯定是我們自己的事情了。。。接下來的工作是什麼?MTD原始設備和硬件驅動層的交互了.這個纔是我們要去真正實現的。。

進過前面3篇文章對NANDFLASH的一些硬件特性以及MTD的上層操作已經有了一個大體概念,這些東西的重要性就像你要吃飯那麼你首先得學會拿筷子道理一樣吧,應該一樣的。

五、MTD原始設備層和硬件驅動層的橋樑:

   

熟悉這幾個重要的結構體:linux/mtd/mtd.h

struct mtd_info {
    u_char type;               /**內存技術類型(包括MTD_RAM,MTD_ROM,MTD_NANDFLASH等)**/
    uint32_t flags;           /*MTD設備屬性標誌*/
    uint64_t size;     // Total size of the MTD

    uint32_t erasesize;//MTD設備的擦除單元大小,對於NandFlash來說就是Block的大小
   
    uint32_t writesize;//最小的可寫單元的字節數

    uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)
    uint32_t oobavail;  // Available OOB bytes per block

    unsigned int erasesize_shift;
    unsigned int writesize_shift;
    /* Masks based on erasesize_shift and writesize_shift */
    unsigned int erasesize_mask;
    unsigned int writesize_mask;

    // Kernel-only stuff starts here.
    const char *name;
    int index;

    /* ecc layout structure pointer - read only ! */
    struct nand_ecclayout *ecclayout;

    /* Data for variable erase regions. If numeraseregions is zero,
     * it means that the whole device has erasesize as given above.

     *一般爲1吧

     */
    int numeraseregions;

    //擦除區域的指針

    struct mtd_erase_region_info *eraseregions;
    //擦除函數將一個erase_info結構放入擦除隊列中
    int (*erase) (struct mtd_info *mtd, struct erase_info *instr);

    /* This stuff for eXecute-In-Place */
    /* phys is optional and may be set to NULL */
    int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
            size_t *retlen, void **virt, resource_size_t *phys);

    /* We probably shouldn't allow XIP if the unpoint isn't a NULL */
    void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);

    /* Allow NOMMU mmap() to directly map the device (if not NULL)
     * - return the address to which the offset maps
     * - return -ENOSYS to indicate refusal to do the mapping
     */
    unsigned long (*get_unmapped_area) (struct mtd_info *mtd,
                        unsigned long len,
                        unsigned long offset,
                        unsigned long flags);

    /* Backing device capabilities for this device
     * - provides mmap capabilities
     */
    struct backing_dev_info *backing_dev_info;

  //read和write分別用於MTD設備的讀和寫
    int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);

    int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
    //讀寫MTD設備的OOB區域的數據
    int (*read_oob) (struct mtd_info *mtd, loff_t from,
             struct mtd_oob_ops *ops);
    int (*write_oob) (struct mtd_info *mtd, loff_t to,
             struct mtd_oob_ops *ops);
     //訪問一些受保護的寄存器 
    int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
    int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
    int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
    int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);

    /* kvec-based read/write methods.
       NB: The 'count' parameter is the number of _vectors_, each of
       which contains an (ofs, len) tuple.
    */
    int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);

    /* Sync *//*同步*/
    void (*sync) (struct mtd_info *mtd);

    /* Chip-supported device locking */
    int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
    int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);

    /* Power Management functions */
    int (*suspend) (struct mtd_info *mtd);
    void (*resume) (struct mtd_info *mtd);

    /* Bad block management functions */
    int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
    int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);

    struct notifier_block reboot_notifier;  /* default mode before reboot */

    /* ECC status information */
    struct mtd_ecc_stats ecc_stats;
    /* Subpage shift (NAND) */
    int subpage_sft;
   //私有數據 指向map_info結構
    void *priv;

    struct module *owner;
    struct device dev;
    int usecount;

   //設備驅動回調函數

    int (*get_device) (struct mtd_info *mtd);
    void (*put_device) (struct mtd_info *mtd);
};

上面的read()、write()、read_oob()、等都是MTD設備驅動要實現的主要函數,不過這些函數都是透明的不需要我們自己去實現,因爲Linux在MTD的下層實現了針對NORFLASH和NANDFLASH的通用mtd_info成員函數。

感覺沒什麼可寫的了,因爲這些都不是我要關注的東西,但是又不能不知道有這麼回事

這些結構體還是得了解了解

driver/mtd/mtdpart.c

/* Our partition node structure */
struct mtd_part {
    struct mtd_info mtd;    //分區信息
    struct mtd_info *master; //該分區的主分區
    uint64_t offset;              //該分區的偏移量
    struct list_head list;
};

mtd_partition會在MTD原始設備調用add_mtd_partitions()的時候傳遞分區參數/linux/mtd/partition.h

struct mtd_partition {
    char *name;            /* identifier string */
    uint64_t size;            /* partition size */
    uint64_t offset;        /* offset within the master MTD space */
    uint32_t mask_flags;        /* master MTD flags to mask out for this partition */
    struct nand_ecclayout *ecclayout;    /* out of band layout for this partition (NAND only)*/
};

一個MTD原始設備可以通過mtd_part分割成數個MTD原始設備註冊進mtd_table,mtd_table中的每個MTD原始設備都可以被註冊成一個MTD設備,有兩個函數可以完成這個工作,即add_mtd_device函數和add_mtd_partitions函數。

其中add_mtd_device函數是把整個NAND FLASH註冊進MTD Core,而add_mtd_partitions函數則是把NAND FLASH的各個分區分別註冊進MTD Core。

 

int add_mtd_device(struct mtd_info *mtd)

int del_mtd_device(struct mtd_info *mtd)

int add_mtd_partitions(struct mtd_info *master,structmtd_partitions *parts,int nbparts)

int del_mtd_partitions(struct mtd_info *master)

當MTD原始設備調用add_mtd_partitions()的時候它會對每一個新建分區建立一個struct mtd_part 結構將其加入mtd_partitions中並調用add_mtd_device()將此分區做爲MTD設備註冊進MTD Core,成功的時返回0

重點關注一下add_mtd_partitions(struct mtd_info *master,struct mtd_partitions *parts,int nbparts)其中master就是這個MTD原始設備,parts即NAND的分區信息,nbparts指有幾個分區。那麼parts和nbparts怎麼來的呢,其實上面有一句話已經說了。。舉個例子在我們移植Linux內核到我們的開發板的時候我們會對NANDFLASH進行分區 這eilian240_default_nand_part就是起到上面這兩個參數的作用如下:

static struct mtd_partition eilian240_default_nand_part[] = {
    [0] = {
        .name    = "bootloader",/*uboot存放的地址對應dev/mtdblock0*/
        .size    = 0x00040000,  /*大小256KB=((D)(0x00040000))/1024*/
        .offset    = 0,
    },
    [1] = {
        .name    = "param",
        .offset = 0x00040000,/***如果UBOOT比較大就放在這個區域可以將前面的覆蓋掉**//**0x00040000是偏移量**/
        .size    = 0x00020000,  /**128KB**/
    },
    [2] = {
        .name    = "Linux Kernel",/********用於存放Linux內核鏡像dev/mtdblock3*/
        .offset = 0x00060000,
        .size    = 0x00500000,        /*5M*/
    },
    [3] = {
        .name    = "rootfs",
        .offset = 0x00560000,
        .size    = 1024 * 1024 * 1024, //1G因爲該移植同時支持大容量的NAND
    },
    [4] = {
        .name    = "nand",
        .offset = 0x00000000,
        .size    = 1024 * 1024 * 1024, //
    }
};
/*arch/arm/plat-samsung/include/plat/nand.h **/

Linux內核在MTD的下層實現了通用的NAND驅動(/driver/mtd/nand/nand_base.c)因此芯片級的驅動實現不再需要我們關心mtd中的那些成員函數了主題轉移到nand_chip數據結構中

先了解了解nand_chip結構體

struct nand_chip {
    void  __iomem    *IO_ADDR_R;    //讀8位I/O線的地址
    void  __iomem    *IO_ADDR_W;   //寫8位I/O線的地址
    uint8_t        (*read_byte)(struct mtd_info *mtd);//從芯片讀一個字節
    u16        (*read_word)(struct mtd_info *mtd);//從芯片讀一個字
    void        (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);//將緩衝區的數據寫入芯片
    void        (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);//將芯片中的數據獨到緩衝區中

    int        (*verify_buf)(struct mtd_info *mtd, const uint8_t *buf, int len); //驗證芯片和寫入緩衝區中的數據

   int         (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);//檢查是否壞塊
    int        (*block_markbad)(struct mtd_info *mtd, loff_t ofs);//標記壞塊

    void        (*select_chip)(struct mtd_info *mtd, int chip);          //實現選中芯片
    void        (*cmd_ctrl)(struct mtd_info *mtd, int dat,              
                    unsigned int ctrl);//控制ALE/CLE/nCE,也用於寫命令和地址
    int        (*dev_ready)(struct mtd_info *mtd);//設備就緒
    void        (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column, int page_addr); //實現命令發送

    int        (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
    void        (*erase_cmd)(struct mtd_info *mtd, int page);//擦除命令的處理
    int        (*scan_bbt)(struct mtd_info *mtd);//掃描壞塊
    int        (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int status, int page);

    int        (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
                      const uint8_t *buf, int page, int cached, int raw);//寫一頁

    int        chip_delay;//有板決定的延遲時間
   unsigned int    options;//與具體的NAND芯片相關的一些選項,如NAND_NO_AUTOINCR,NAND_BUSWIDTH_16等,至於這些選項具體表示什麼含義,可以參考<linux/mtd/nand.h>,那裏有較爲詳細的說明;

    int        page_shift;//用位表示的NAND芯片的page大小,如某片NAND芯片的一個page有512個字節,那麼page_shift就是9;
    int        phys_erase_shift;//用位表示的NAND芯片的每次可擦除的大小,如某片NAND芯片每次可擦除16K字節(通常就是一個block的大小),那麼phys_erase_shift就是14;
    int        bbt_erase_shift;//用位表示的bad block table的大小,通常一個bbt佔用一個block,所以bbt_erase_shift通常與phys_erase_shift相等;
    int        chip_shift;用位表示的NAND芯片的容量;
    int        numchips;表示系統中有多少片NAND芯片;
    uint64_t    chipsize;//NAND芯片的大小;
    int        pagemask;//計算page number時的掩碼,總是等於chipsize/page大小- 1;
    int        pagebuf;用來保存當前讀取的NAND芯片的page number,這樣一來,下次讀取的數據若還是屬於同一個page,就不必再從NAND芯片讀取了,而是從data_buf中直接得到;
    int        subpagesize;
    uint8_t        cellinfo;
    int        badblockpos;
    int        badblockbits;

    flstate_t    state;

    uint8_t        *oob_poi;
    struct nand_hw_control  *controller;
    struct nand_ecclayout    *ecclayout;

    struct nand_ecc_ctrl ecc;
    struct nand_buffers *buffers;
    struct nand_hw_control hwcontrol;

    struct mtd_oob_ops ops;

    uint8_t        *bbt;
    struct nand_bbt_descr    *bbt_td;
    struct nand_bbt_descr    *bbt_md;

    struct nand_bbt_descr    *badblock_pattern;

    void        *priv;

};


這上面有一個與ECC相關的結構體 struct nand_ecc_ctrl

struct nand_ecc_ctrl {
    nand_ecc_modes_t    mode;
    int            steps;
    int            size;
    int            bytes;
    int            total;
    int            prepad;
    int            postpad;
    struct nand_ecclayout    *layout;
    void            (*hwctl)(struct mtd_info *mtd, int mode);/**控制硬件ECC*/
    int            (*calculate)(struct mtd_info *mtd,
                         const uint8_t *dat,
                         uint8_t *ecc_code);/**根據data計算ecc值**/
    int            (*correct)(struct mtd_info *mtd, uint8_t *dat,
                       uint8_t *read_ecc,
                       uint8_t *calc_ecc);
    int            (*read_page_raw)(struct mtd_info *mtd,
                         struct nand_chip *chip,
                         uint8_t *buf, int page);/**向NANDFLASH芯片讀一個頁的原始數據*/
    void            (*write_page_raw)(struct mtd_info *mtd,
                          struct nand_chip *chip,
                          const uint8_t *buf);
    int            (*read_page)(struct mtd_info *mtd,
                         struct nand_chip *chip,
                         uint8_t *buf, int page);/**讀一個頁但包含ecc校驗*/
    int            (*read_subpage)(struct mtd_info *mtd,
                         struct nand_chip *chip,
                         uint32_t offs, uint32_t len,
                         uint8_t *buf);
    void            (*write_page)(struct mtd_info *mtd,
                          struct nand_chip *chip,
                          const uint8_t *buf);
    int            (*read_oob)(struct mtd_info *mtd,
                        struct nand_chip *chip,
                        int page,
                        int sndcmd);/**讀OOB但不包含MAIN部分**/
    int            (*write_oob)(struct mtd_info *mtd,
                         struct nand_chip *chip,
                         int page);
};


hwctl:這個函數用來控制硬件產生ecc,其實它主要的工作就是控制NAND controller向NAND芯片發出NAND_ECC_READ、NAND_ECC_WRITE和NAND_ECC_READSYN等命令,與struct nand_chip結構體中的cmdfunc類似,只不過發起的命令是ECC相關的罷了;
 
calculate:根據data計算ecc值;
 
correct:根據ecc值,判斷讀寫數據時是否有錯誤發生,若有錯,則立即試着糾正,糾正失敗則返回錯誤;
 
read_page_raw和write_page_raw:從NAND芯片中讀取一個page的原始數據和向NAND芯片寫入一個page的原始數據,所謂的原始數據,即不對讀寫的數據做ecc處理,該讀寫什麼值就讀寫什麼值。另外,這兩個函數會讀寫整個page中的所有內容,即不但會讀寫一個page中MAIN部分,還會讀寫OOB部分。
 
read_page和write_page:與read_page_raw和write_page_raw類似,但不同的是,read_page和write_page在讀寫過程中會加入ecc的計算,校驗,和糾正等處理。
read_oob和write_oob:讀寫oob中的內容,不包括MAIN部分。
 
其實,以上提到的這幾個read_xxx和write_xxx函數,最終都會調用struct nand_chip中的read_buf和write_buf這兩個函數,所以如果沒有特殊需求的話,我認爲不必自己實現,使用MTD提供的default的函數即可。


 


下面這個結構體用來ECC在oob中佈局的一個結構體。

struct nand_ecclayout {
    __u32 eccbytes;//ecc字節數,對於512字節/page的NANDflash,eccbytes=3,如果需要額外用到oob中的數據,那麼也可以大於3.
    __u32 eccpos[64];ECC數據在oob中的位置,這裏之所以是個64字節的數組,是因爲對於2K/頁NAND來說,它的oob64個字節。而對於512字節/pageNAND來說,可以而且只可以定義它的前16個字節。
    __u32 oobavail;OOB中可用的字節數,不需要對該成員賦值,MTD會根據其他三個成員計算出來
    struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];//顯示定義空閒的OOB字節
};

大家都知道OOB但是OOB裏面究竟存了些什麼,OOB主要用來存儲兩種信息:壞塊信息和ECC數據,對於小頁的NANDFLASH一般壞塊佔據一個字節(並且是在第6個字節),ECC佔3個字節,上面這個結構體就是起到了這個作用告訴那些與操作ECC無關的函數,在OOB區域裏那部分是來存儲ECC的(不可他用),那些字節是空閒的

實在寫不下去了,仔細的想了一想還是把mtd/nand/s3c2410.c好好的分析分析

在Linux中NANDFLASH設備驅動是被註冊成平臺驅動的。我還是從函數的入口出發一步一個腳印的分析。突然間發現這些代碼真的很經典

由於這一次CPU是S3C2440所以分析過程中會把其他的CPU去掉

七、mtd/nand/s3c2410.c函數的解析

1、函數中出現的幾個結構體

struct s3c24xx_nand_mtd {
    struct mtd_info            mtd;/*用來表述MTD原始設備的結構*/
    struct nand_chip        chip;/*該結構體表示一個NAND芯片*/

   //該結構定義在arch/arm/plat-s3c/include/plat/nand.h中,該結構體包含了一個或多個的NAND芯片的信息

    struct s3c2410_nand_set        *set;
    struct s3c24xx_nand_info    *info;
    int                scan_res;
};
/**CPU選型的枚舉**/
enum s3c_cpu_type {
    TYPE_S3C2410,
    TYPE_S3C2440,
};
/**NANDFLASH控制器狀態的結構**/
struct s3c24xx_nand_info {
    /* mtd info */

   //include/linux/mtd/nand.h硬件控制器結構

    struct nand_hw_control        controller;
    struct s3c24xx_nand_mtd        *mtds;

   //NANDFLASH平臺驅動結構

    struct s3c2410_platform_nand    *platform;

    /* device info */
    struct device            *device;
    struct resource            *area;
    struct clk            *clk;
    void __iomem            *regs;
    void __iomem            *sel_reg;
    int                sel_bit;
    int                mtd_count;
    unsigned long            save_sel;
    unsigned long            clk_rate;

    enum s3c_cpu_type        cpu_type;

#ifdef CONFIG_CPU_FREQ
    struct notifier_block    freq_transition;
#endif
};

2、函數入口和出口

static struct platform_driver s3c24xx_nand_driver = {
    .probe        = s3c24xx_nand_probe,
    .remove        = s3c24xx_nand_remove,
    .suspend    = s3c24xx_nand_suspend,
    .resume        = s3c24xx_nand_resume,
    .id_table    = s3c24xx_driver_ids,
    .driver        = {
        .name    = "s3c24xx-nand",
        .owner    = THIS_MODULE,
    },
};

static int __init s3c2410_nand_init(void)
{
    printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");

    //這些已經沒什麼好解釋的了。註冊平臺驅動
    return platform_driver_register(&s3c24xx_nand_driver);
}

static void __exit s3c2410_nand_exit(void)
{

    //當我們卸載內核模塊的時候會唄調用 註銷平臺驅動

    platform_driver_unregister(&s3c24xx_nand_driver);
}

module_init(s3c2410_nand_init);
module_exit(s3c2410_nand_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ben Dooks <[email protected]>");
MODULE_DESCRIPTION("S3C24XX MTD NAND driver");

3、NANDFLASH中的s3c24xx_nand_probe函數。

<隨便提一句>可能我們都懂當我們向系統註冊一個平臺驅動的時候,會去遍歷系統上的平臺設備,找到能夠與之相對應的平臺設備。當找到可用的設備後該函數纔會被調用<稱之探測函數>對於外設來說驅動的本身應該是從這個探測函數真正開始的,那麼它究竟有什麼用呢?它到底做了什麼事情,這可能是我們最關心的。在MTD/NAND的probe過程中,去用clk_enable打開Nand Flash控制器的clock時鐘,用request_mem_region去申請驅動所需要的一些內存等相關資yuan然後,在 s3c2410_nand_inithw 中,去初始化硬件相關的部分,主要是關於時鐘頻率的計算,以及啓用 Nand Flash 控制器,使得硬件初始化好了,後面才能正常工作。如果說把這個函數理解透了那麼整個驅動起碼是完成三分之二的工作了。在s3c2410.c中s3c24xx_nand_probe函數特別的長,所以一段一段的分析.

static int s3c24xx_nand_probe(struct platform_device *pdev)
{

   /**從總線設備中取出平臺設備的數據,我一直在思索最後這個結構會從哪裏傳進來呢?**如果你移植過Linux到你的開發板那麼你一定會在你的板層文件中構建這樣一個結構體請看下面步驟a)中的結構體*/

    struct s3c2410_platform_nand *plat = to_nand_plat(pdev);

    enum s3c_cpu_type cpu_type;
    struct s3c2410_nand_info *info;
    struct s3c2410_nand_mtd *nmtd;
    struct s3c2410_nand_set *sets;
    struct resource *res;
    int err = 0;
    int size;
    int nr_sets;
    int setno;
    /*獲取CPU類型*/
    cpu_type = platform_get_device_id(pdev)->driver_data;

    pr_debug("s3c2410_nand_probe(%p)\n", pdev);
    /*爲s3c2410_nand_info分配內存*/
    info = kmalloc(sizeof(*info), GFP_KERNEL);
    if (info == NULL) {
        dev_err(&pdev->dev, "no memory for flash info\n");
        err = -ENOMEM;
        goto exit_error;
    }
    /**初始化s3c2410_nand_info**/
    memset(info, 0, sizeof(*info));
    /**將nand設備的數據信息傳遞到系統平臺設備中去**/
    platform_set_drvdata(pdev, info);
    /**初始化自旋鎖和等待隊列**/
    spin_lock_init(&info->controller.lock);
    init_waitqueue_head(&info->controller.wq);

    /* get the clock source and enable it */

    info->clk = clk_get(&pdev->dev, "nand");
    if (IS_ERR(info->clk)) {
        dev_err(&pdev->dev, "failed to get clock\n");
        err = -ENOENT;
        goto exit_error;
    }

    clk_enable(info->clk);//使能時鐘

    /* allocate and map the resource */
     
    /* currently we assume we have the one resource */
    res  = pdev->resource;
    size = res->end - res->start + 1;
    /**爲資源申請IO內存**/
    info->area = request_mem_region(res->start, size, pdev->name);

    if (info->area == NULL) {
        dev_err(&pdev->dev, "cannot reserve register region\n");
        err = -ENOENT;
        goto exit_error;
    }
    /*****這都是給info這個結構的成員變量賦值啊*沒啥可說的,*/
    info->device     = &pdev->dev;
    info->platform   = plat;
    info->regs       = ioremap(res->start, size);
    info->cpu_type   = cpu_type;

    if (info->regs == NULL) {
        dev_err(&pdev->dev, "cannot reserve register region\n");
        err = -EIO;
        goto exit_error;
    }

    dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);

    /* initialise the hardware *//**解析c*/
    /**初始化硬件**/
    err = s3c2410_nand_inithw(info);
    if (err != 0)
        goto exit_error;
     /*struct s3c2410_platform_nand 中包含了一個struct s3c2410_nand_set *sets
    的成員變量 專門用來描述芯片信息的**/
    sets = (plat != NULL) ? plat->sets : NULL;
    nr_sets = (plat != NULL) ? plat->nr_sets : 1;

    info->mtd_count = nr_sets;

    /* allocate our information */

    size = nr_sets * sizeof(*info->mtds);
    info->mtds = kmalloc(size, GFP_KERNEL);
    if (info->mtds == NULL) {
        dev_err(&pdev->dev, "failed to allocate mtd storage\n");
        err = -ENOMEM;
        goto exit_error;
    }

    memset(info->mtds, 0, size);

    /* initialise all possible chips */

    nmtd = info->mtds;

    for (setno = 0; setno < nr_sets; setno++, nmtd++) {
        pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);
       /**nand_chip結構的初始化**見下一遍分析d)**/
        s3c2410_nand_init_chip(info, nmtd, sets);
        /**讀取芯片的ID**/
        nmtd->scan_res = nand_scan_ident(&nmtd->mtd,
                         (sets) ? sets->nr_chips : 1);

        if (nmtd->scan_res == 0) {
            s3c2410_nand_update_chip(info, nmtd);
            nand_scan_tail(&nmtd->mtd);
            s3c2410_nand_add_partition(info, nmtd, sets);
        }

        if (sets != NULL)
            sets++;
    }

    err = s3c2410_nand_cpufreq_register(info);
    if (err < 0) {
        dev_err(&pdev->dev, "failed to init cpufreq support\n");
        goto exit_error;
    }

    if (allow_clk_stop(info)) {
        dev_info(&pdev->dev, "clock idle support enabled\n");
        clk_disable(info->clk);
    }

    pr_debug("initialised ok\n");
    return 0;

 exit_error:
    s3c24xx_nand_remove(pdev);

    if (err == 0)
        err = -EINVAL;
    return err;
}

我真不知道該怎麼去分析這個程序。

    a)分析該函數的第一行的代碼我就迷茫了呵呵.看看下面這個結構體.你是否會發現什麼
static struct s3c2410_platform_nand eilian240_nand_info = {
    .tacls        = 20,
    .twrph0        = 60,
    .twrph1        = 20,
    .nr_sets    = ARRAY_SIZE(eilian240_nand_sets),
    .sets        = eilian240_nand_sets,
    .ignore_unset_ecc = 1,
};發現了吧這些芯片硬件信息是在我們移植的時候才傳進去的<所以這就和目標板有關係了>

  那麼這些芯片信息又是怎樣和系統裏面的總線設備關聯起來的呢?看下面這三句

   info = kmalloc(sizeof(*info), GFP_KERNEL); 爲info結構分配內存

   memset(info, 0, sizeof(*info));              //初始化內存
   platform_set_drvdata(pdev, info); //別小看這一句,功能可強大了,這些芯片信息就是通過它傳遞到總線設備裏面去的,看下源代碼你就明白了

    b)關於時鐘使能和平臺設備驅動程序,想設備申請資源這是必須要做的事情,我解釋不了,表達能力很有限哦。

    c)硬件的初始化函數s3c2410_nand_inithw(info);其實就是在設置一些寄存器和時鐘的頻率,看看它是怎麼實現的

static int s3c2410_nand_inithw(struct s3c2410_nand_info *info)
{
    int ret;

    ret = s3c2410_nand_setrate(info);
    if (ret < 0)
        return ret;

     switch (info->cpu_type) {
     case TYPE_S3C2410:
    default:
        break;

     case TYPE_S3C2440:
     case TYPE_S3C2412:
        /* enable the controller and de-assert nFCE */
        writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
    }

    return 0;
}

static int s3c2410_nand_setrate(struct s3c2410_nand_info *info)
{
    struct s3c2410_platform_nand *plat = info->platform;
    int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;
    int tacls, twrph0, twrph1;
    /**獲取時鐘頻率**/
    unsigned long clkrate = clk_get_rate(info->clk);//這個時鐘我們在之前已經獲取了吧
    unsigned long uninitialized_var(set), cfg, uninitialized_var(mask);//這其實都是一些宏,看看源代碼就知道了    unsigned long flags;

    /* calculate the timing information for the controller */

    info->clk_rate = clkrate;
    clkrate /= 1000;    /* turn clock into kHz for ease of use */

    if (plat != NULL) {
        tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);
        twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);
        twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);
    } else {
        /* default timings */
        tacls = tacls_max;
        twrph0 = 8;
        twrph1 = 8;
    }

    if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
        dev_err(info->device, "cannot get suitable timings\n");
        return -EINVAL;
    }

    dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n",
           tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate));

    switch (info->cpu_type) {
。。。。。。。
    case TYPE_S3C2440:
    case TYPE_S3C2412:
        mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) |
            S3C2440_NFCONF_TWRPH0(7) |
            S3C2440_NFCONF_TWRPH1(7));

        set = S3C2440_NFCONF_TACLS(tacls - 1);
        set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
        set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
        break;

    default:
        BUG();
    }

    local_irq_save(flags);

    cfg = readl(info->regs + S3C2410_NFCONF);
    cfg &= ~mask;
    cfg |= set;
    writel(cfg, info->regs + S3C2410_NFCONF);

    local_irq_restore(flags);

    dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg);

    return 0;
}

在分析這幾段代碼的時候我爲Nandflash存儲的時序糾結了好久,看看下面這個圖,S3C2440數據手冊上的

   

TACLS:當CLE/ALE使能後再過多長的時間nWe纔有效/* time for active CLE/ALE to nWE/nOE */

TWRPH0:nWE/nRE的有效時間的長度                       /* active time for nWE/nOE */

TWRPH1:nWE/nRE無效開始到CLE/ALE也無效的時間的長度 /* time for release CLE/ALE from nWE/nOE inactive */

不知道是不是這樣的....如果分析錯了有大神看到請指出。在k9f2g08上有這樣一個表

   

所以有a中的:

    .tacls            = 20,
    .twrph0        = 60,
    .twrph1        = 20,    不過到這個地方我還是有一點點的迷惑不知道是我的數據手冊錯了還是什麼,隨便找了一個時序看了看時間不對啊。。。。。

 int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;//這一句先看看下面這個表2<對下面分析還要用到的>

   

紅色標註的部分不是明明是0---3麼 怎麼這裏設置的4呢?別急繼續往下看。。

tacls ,twrph0,twrph1它是怎麼進行轉換的呢

static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max)
{
    int result;
    /**[(wanted * clk)+NS_IN_KHZ)-1]/NS_IN_KHZ */
    result = DIV_ROUND_UP((wanted * clk), NS_IN_KHZ);
   /** #define NS_IN_KHZ 1000000      //原本單位是HZ現在轉換成KHZ**/
    pr_debug("result %d from %ld, %d\n", result, clk, wanted);

    if (result > max) {
        printk("%d ns is too big for current clock rate %ld\n", wanted, clk);
        return -1;
    }

    if (result < 1)
        result = 1;

    return result;
}

現在再往上看那段if(plat != NULL){。。。。。。。。}或許明白了

再返回 int s3c2410_nand_setrate(struct s3c2410_nand_info *info)中下面這段代碼

switch (info->cpu_type) {
。。。。。。。
    case TYPE_S3C2440:
    case TYPE_S3C2412:

        /**好了現在來了個tacls_max - 1或許就明白了上面爲何是4而不是3了。這些代碼都是在給NANDFLASH配置寄存器的相應的位賦值,不過還沒有寫進去。後面有寫入的操作的。*/

        mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) |
            S3C2440_NFCONF_TWRPH0(7) |
            S3C2440_NFCONF_TWRPH1(7));

        set = S3C2440_NFCONF_TACLS(tacls - 1);
        set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
        set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
        break;

        。。。。。。。。。。。。。

      /**將這些數據寫進寄存器中去*/

    cfg = readl(info->regs + S3C2410_NFCONF);
    cfg &= ~mask;
    cfg |= set;
    writel(cfg, info->regs + S3C2410_NFCONF);

再返回static int s3c2410_nand_inithw(struct s3c2410_nand_info *info)有這麼一斷代碼

。。。。。。。。

   case TYPE_S3C2440:
     case TYPE_S3C2412:
        /* enable the controller and de-assert nFCE */
      /*****使能NANDFLASH控制寄存器******/
        writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
    }

/***********到這裏爲止這個硬件的初始化工作就結束了***************************************************/

  上面基本的硬件初始化完後應該是要初始化nand_chip實例了,並運行nand_scan()掃描NAND設備了,最後添加板文件中的分區表。nand_chip是NANDFLASH驅動的核心結構上一遍文章已經分析過了。還是放到下一遍文章吧

初始化基本的硬件配置後probe函數就會開始與NAND芯片進行交互了,它要做的事情主要包括這幾個方面:讀取NAND芯片的ID,然後查表得到這片NAND芯片的如廠商,page size,erase size以及chip size等信息,接着,根據struct nand_chip中options的值的不同,或者在NAND芯片中的特定位置查找bad block table,或者scan整個NAND芯片,並在內存中建立bad block table。這些都由nand_scan()完成。

nand_scan函數主要有兩個兩個函數組成,即nand_scan_ident函數和nand_scan_tail函數。其中nand_scan_ident函數會讀取NAND芯片的ID,而nand_scan_tail函數則會查找或者建立bbt (bad block table)。

最後調用add_mtd_partitions()添加板層文件platform中定義的分區表。

      d)nand_chip的初始化,關於nand_chip上第5編文章中已介紹

 /**初始化nand_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;//&nmtd->chip=&(nmtd->chip)
    void __iomem *regs = info->regs;//用於保存地址的。。
     /**下面這一段是在給nand_chip中的函數指針賦值**/
    chip->write_buf    = s3c2410_nand_write_buf;
    chip->read_buf     = s3c2410_nand_read_buf;
    chip->select_chip  = s3c2410_nand_select_chip;
    chip->chip_delay   = 50;//延遲時間
    chip->priv       = nmtd; //這個是很重要的將struct s3c2410_nand_mtd賦值給nand_chip的私有數據成員
    chip->options       = 0;//在第5篇文章中有介紹這個地方好像錯了,或許後面會有賦值,因爲沒有0定義這個宏
    chip->controller   = &info->controller;//指向struct nand_hw_control  的指針
 。。。。。。。。。。。。。。。。
    case TYPE_S3C2440:
        chip->IO_ADDR_W = regs + S3C2440_NFDATA;//2440NAND數據寄存器
        info->sel_reg   = regs + S3C2440_NFCONT;//2440NAND控制寄存器
        info->sel_bit    = S3C2440_NFCONT_nFCE;//1<<1,此刻芯片片選信號爲disable,默認就是disable
        chip->cmd_ctrl  = s3c2440_nand_hwcontrol;//控制ALE/CLE/nCE,也用於寫命令和地址
        chip->dev_ready = s3c2440_nand_devready;//設備就緒
        chip->read_buf  = s3c2440_nand_read_buf;//將芯片中的數據讀到緩衝區中
        chip->write_buf    = s3c2440_nand_write_buf;//將緩衝區中的數據寫入芯片
        break;

        。。。。。。。。。。。。。。。。
      }

    chip->IO_ADDR_R = chip->IO_ADDR_W;

    nmtd->info       = info;
    nmtd->mtd.priv       = chip; //把指向struct nand_chip結構體的指針賦給struct mtd_infopriv成員變量,因爲MTD Core中很多函數之間的調用都只傳遞struct mtd_info,它需要通過priv成員變量得到struct nand_chip
    nmtd->mtd.owner    = THIS_MODULE;
    nmtd->set       = set;
    /**如果採用硬件ECC**/
    if (hardware_ecc) {
        chip->ecc.calculate = s3c2410_nand_calculate_ecc;
        chip->ecc.correct   = s3c2410_nand_correct_data;
        chip->ecc.mode        = NAND_ECC_HW;
        switch (info->cpu_type) {
     。。。。。。。。。。。。。。。。
        case TYPE_S3C2440:
              chip->ecc.hwctl     = s3c2440_nand_enable_hwecc;
              chip->ecc.calculate = s3c2440_nand_calculate_ecc;
            break;
        }
    } else {

       //使用軟件校驗

        chip->ecc.mode        = NAND_ECC_SOFT;
    }
      /**ECC*/
    if (set->ecc_layout != NULL)
        chip->ecc.layout = set->ecc_layout;
     /**禁止ECC*/
    if (set->disable_ecc)
        chip->ecc.mode    = NAND_ECC_NONE;

    switch (chip->ecc.mode) {
    。。。。。。。。。。。
    }

    /* If you use u-boot BBT creation code, specifying this flag will
     * let the kernel fish out the BBT from the NAND, and also skip the
     * full NAND scan that can take 1/2s or so. Little things... */
    if (set->flash_bbt)//當flashbbt=1的時候系統在啓動的時候將跳過對bbt的掃描
        chip->options |= NAND_USE_FLASH_BBT | NAND_SKIP_BBTSCAN;
}

接下來是讀取芯片的ID調用int nand_scan_ident(struct mtd_info *mtd, int maxchips)函數該函數是通用的,定義在nand_base.c中暫時還沒研究過

/**讀取芯片的ID**/
        nmtd->scan_res = nand_scan_ident(&nmtd->mtd,
                         (sets) ? sets->nr_chips : 1);
     
        if (nmtd->scan_res == 0) {  /**如果讀取成功則返回0**/
            s3c2410_nand_update_chip(info, nmtd);//更新,下面看看它的原型
            nand_scan_tail(&nmtd->mtd);//查找或者建立bbt(bad block table)
            s3c2410_nand_add_partition(info, nmtd, sets);//添加分區,與板層文件相關

           //這個函數的實現那肯定就是調用add_mtd_device(&mtd->mtd)。。。

        }

    e)s3c2410_nand_update_chip(struct s3c2410_nand_info *info,
                     struct s3c2410_nand_mtd *nmtd)分析

static void s3c2410_nand_update_chip(struct s3c2410_nand_info *info,
                     struct s3c2410_nand_mtd *nmtd)
{
    struct nand_chip *chip = &nmtd->chip;

    dev_dbg(info->device, "chip %p => page shift %d\n",
        chip, chip->page_shift);

    if (chip->ecc.mode != NAND_ECC_HW)//如果不是硬件校驗則直接返回
        return;

        /* change the behaviour depending on wether we are using
         * the large or small page nand device */

    if (chip->page_shift > 10) { //page_shift用位來表示頁的大小 大於2KB的大頁
        chip->ecc.size        = 256;
        chip->ecc.bytes        = 3;
    } else {      小頁
        chip->ecc.size        = 512;
        chip->ecc.bytes        = 3;
        chip->ecc.layout    = &nand_hw_eccoob;
    }

     對於這些關於ECC_LAYOUT的暫時還是不怎麼懂的。。。。。。

}

看看上面用到的nand_hw_eccoob它是一個結構體用來管理OOB中的ECC和壞塊的(第5篇中有詳細的說明)

static struct nand_ecclayout nand_hw_eccoob = {
    .eccbytes = 3,
    .eccpos = {0, 1, 2},//ECC在OOB中的位置
    .oobfree = {{8, 8}}//空閒的OOB字節區域
};

*******************

在上面初始化nand_chip的時候,其中nand_chip裏面有一個這樣的(struct nand_ecc_ctrl)結構體.裏面有許多的成員函數,在初始化的過程中都賦上了值同時nand_chip中也有許多成員函數賦上了值.。至於它們是怎麼實現的,看看返回去看看他們的實現。難度不大。。。。。


 

發佈了51 篇原創文章 · 獲贊 18 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章