1. 程式人生 > >高通AR93XX QCA95XX系列CPU上除錯32MB SPI Nor Flash

高通AR93XX QCA95XX系列CPU上除錯32MB SPI Nor Flash

專案需求,需要基於Openwrt BB1407在一款QCA9561上除錯一塊32MB的SPI Nor Flash晶片(mx25l2563xx)。一開始產品設計的時候,曾經論證過高通9xxx系列CPU能不能支援32MB Nor Flash的問題。那時想當然的認為,SPI Nor Flash不就是基於SPI匯流排訪問的普通裝置嘛,只要SPI匯流排正常,跟容量大小又有什麼關係呢?當真正開始調的時候,才發現自己想的太簡單了。

1. Flash地址模式問題

16MB以下的flash,在SPI總線上進行命令定址操作的時候,地址引數共3個byte,即24-bit模式,而超過16M以後,地址引數就變成了4byte,即32-bit模式。超過16MB的SPI Nor Flash,一般都會有地址模式轉換命令,比如mx25l2563xx,它的地址轉換命令為:EN4B和EX4B,使得Flash可以在兩個模式之間自由切換。地址模式除了關乎正確定址之外,還會關係到重啟是否正常。
SPI Nor Flash是不支援普通Nor Flash那樣直接定址讀取資料的,要使CPU基於SPI Nor Flash正常啟動,需要CPU在啟動時能夠自動從Flash晶片起始位置讀取一定量的內容,方能保證系統可以正常啟動。對此,一般CPU會要求SPI Nor Flash處於3byte模式(至少高通這幾款是這樣的)。
在基於Openwrt bb1407的版本中,Linux核心已經能夠支援mx25l2563xx,驅動初始化時,會讓Flash進入到4byte模式,當此時執行重啟操作,系統將不會正常重啟。解決這個問題通常有兩種方法:

方法一

在linux的reboot過程中,會去呼叫Flash驅動的remove操作,所以對於重啟不正常的問題,解決方法就是針對32M的Flash執行一個Reset或是EX4B命令,執行完畢後,系統就可以正常重啟了。

/* drivers/mtd/devices/m25p80.c */
/* Reset opcodes */
#define OPCODE_RSTEN        0x66    /* Enable reset */
#define OPCODE_RESET        0x99    /* Reset device */

/*
 * Reset whole flash chip. Only be avaliable for chips size >= 32MiB
*/
static inline int flash_4byte_mode_reset(struct m25p *flash) { struct spi_transfer t[2]; struct spi_message m; char command[2]; int ret; if (flash->addr_width != 4) return 1; spi_message_init(&m); memset(t, 0, (sizeof t)); t[0].tx_buf = &command[0
]; t[0].len = 1; t[0].cs_change = 1; spi_message_add_tail(&t[0], &m); t[1].tx_buf = &command[1]; t[1].len = 1; t[1].cs_change = 1; spi_message_add_tail(&t[1], &m); command[0] = OPCODE_RSTEN; command[1] = OPCODE_RESET; mutex_lock(&flash->lock); ret = wait_till_ready(flash); if (ret) { mutex_unlock(&flash->lock); return 1; } spi_sync(flash->spi, &m); mutex_unlock(&flash->lock); return 0; } static int m25p_remove(struct spi_device *spi) { struct m25p *flash = dev_get_drvdata(&spi->dev); int status; /* Reset flash which is in 4-byte mode, since the system reset must run in 3byte mode */ flash_4byte_mode_reset(flash); /* Clean up MTD stuff. */ status = mtd_device_unregister(&flash->mtd); if (status == 0) { kfree(flash->command); kfree(flash); } return 0; }

方法二

一般超過16MB的SPI Nor Flash都會有一些特殊的4byte模式操作命令,比如這款mx25l2563xx,通過檢視其手冊,可知以下命令:
4byte 命令
由上圖可知,使用命令READ4B、PP4B、BE4B就可以對整個32MB的地址空間進行讀寫、擦除操作,而這個過程不需要對Flash執行地址模式切換操作,這樣執行reboot的時候就跟原來一樣,不會產生問題。其他廠商的Flash晶片都有類似命令,當然這個改動會稍微大一些,而且要隨著Flash晶片的不同做不同的命令配置,維護起來感覺要麻煩一些。

2. 高通CPU對SPI Nor Flash特殊的讀取優化

以前在除錯mtk mt7620a和高通的ar934x系列uboot的時候曾經遇到過一個問題,就是mtk的機器在uboot裡面進行SPI Nor Flash讀寫操作後,很容易產生丟內容的問題。跟蹤了下,發現是因為一開始相關功能是在ar934x上面除錯,然後功能比較通用,於是程式碼就直接移到mt7620a上來了。在ar934x上面,對SPI Nor Flash的讀取訪問,直接用了memcpy方式,高通的機器沒有問題,但是同樣的程式碼在mt7620a上就不對了。
按說SPI的訪問,怎麼能夠用memcpy操作呢?在mt7620a上顯然是這裡出了問題,老老實實改回標準SPI讀寫方式,問題就解決了。那反過來,為何ar934x這樣使用沒有問題呢?由於該訪問方式是直接照搬了高通提供的uboot原生程式碼,再加上事物繁忙,對於這點雖然有疑問,但一直沒詳加研究。-__-|| 一直到這次除錯32MB的Flash才對這個問題有了更進一步的認識。

高通9xxx的地址空間
通常,訪問SPI Nor Flash都是基於SPI匯流排介面,序列讀取。但是高通的CPU對此做了一個硬體優化,可以將16MB及以下的Flash地址空間對映到一個記憶體空間直接訪問(對於這些MIPS核心的高通CPU,該段空間屬於kseg1區域),這樣當要讀取這些flash上的內容時,程式碼可以採用memcpy模式,很好的提升了讀取速度。
對應程式碼為:

/* drivers/spi/spi-ath79.c */
static int ath79_spi_setup_transfer(struct spi_device *spi,
                    struct spi_transfer *t)
{
    struct ath79_spi *sp = ath79_spidev_to_sp(spi);
    struct ath79_spi_controller_data *cdata;
    int ret;

    ret = spi_bitbang_setup_transfer(spi, t);
    if (ret)
        return ret;

    cdata = spi->controller_data;
    /* 當is_flash置位時,採用memcpy方式,否則使用標準SPI方式 */
    if (cdata->is_flash)
        sp->bitbang.txrx_bufs = ath79_spi_txrx_bufs;
    else
        sp->bitbang.txrx_bufs = spi_bitbang_bufs;

    return ret;
}

一般在SPI Nor Flash驅動註冊時,都會把這個is_flash置位。但是對於32MB Flash,一旦進入到這個4byte模式,這個硬體機制將不再有效,由於地址空間變了,讀取都變得不正常,會導致啟動核心失敗。此時必須切回到標準的SPI操作,也就是將上述的is_flash清0,當然這會讓讀取速度慢很多。

/* arch/mips/ath79/dev-m25p80.c */
/* 針對32M flash,需要使用標準SPI 操作,不能使用AR 的memory copy */
void ath79_m25p80_use_raw_spi_txrx(void)
{
    ath79_spi0_cdata.is_flash = 0;
}
/* arch/mips/ath79/mach-xxx.c */

static void read_flash_id(void)
{
    u32 rd;

    ath_reg_wr_nf(ATH_SPI_FS, 1);
    ath_reg_wr_nf(ATH_SPI_WRITE, ATH_SPI_CS_DIS);
    ath_spi_bit_banger(ATH_SPI_CMD_RDID);
    ath_spi_delay_8();
    ath_spi_delay_8();
    ath_spi_delay_8();
    ath_spi_go();

    rd = ath_reg_rd(ATH_SPI_RD_STATUS);
    ath_reg_wr_nf(ATH_SPI_FS, 0);
    switch(rd){
        case 0xc22019:
            board_flash_4byte_mem_mode = 1;
            break;
        /* 其他大於16M 的SPI NOR FLASH */
        case xxxx:
        default:
            board_flash_4byte_mem_mode = 0;
            break;
    }

}

/* xx_setup */
static void xx_setup(void)
{
    ....
    read_flash_id();
    if(board_flash_4byte_mem_mode)
        ath79_m25p80_use_raw_spi_txrx();
    ...
}

假如仍然讓Flash保持在3byte模式,則還是可以正常使用,但是隻能正常使用前16MB的空間。

2.1 使用硬體優化方式與傳統SPI方式的效能對比

通過對一顆16MB的Flash晶片,分別使用高通硬體優化方式和標準SPI讀取方式,基於MTD命令讀寫命令,進行了簡單的讀寫效能對比,結果如下:
memcpy模式,
讀 15MB 9s 1.667MB/S
寫 15MB 159s 0.094MB/S

SPI標準模式,
讀15MB 18s 0.833MB/S
寫 15MB 159s 0.094MB/S
由上可知,當使用SPI標準模式時,讀取速度降了一半。

3. 無線驅動載入問題

高通的wifi驅動,一般需要有一個ART MTD分割槽,用來存放一些與wifi射頻相關的引數,以供驅動啟動時利用這些引數來做初始化配置。
通常這些無線引數都是在核心啟動早期,通過memcpy的方式從SPI Nor Flash上面讀取到特定的記憶體位置,現在變成32MB後,超過16MB的位置將無法繼續使用這種方法訪問。解決方法就是在這個位置判斷Flash型號,如果Flash容量超過32MB,並且ART區還是在Flash的最後64KB,那麼可以做個標記,等整個MTD起來後且在無線驅動載入之前,通過MTD的介面來進行引數區讀取。
如果還是想用memcpy方式,可以考慮把ART區放在前面16MB,應該也是可以的,只是這樣的話,改動牽扯麵可能就比較大了,無線相關的引數校準、驅動載入都需要調整。

4. 後記

把上述幾個問題都搞清楚後,在高通CPU上除錯32MB Flash也就水到渠成,迎刃而解了。MT7620A並沒有這樣的硬體優化機制,所以在前面提到的uboot中使用高通的程式碼顯然就會出錯了(掉以輕心的ctrl+c ctrl+v害死人呀)。也難怪高通的片子要比MT7620A貴不少呢。
不過便宜有便宜的好處,至少在mt7620a上除了Flash地址模式這一共性問題需要注意外,其他方面都應該不用做大的修改了。

參考資料