傳輸方式 DIO/QIO/DOUT/QPI
- QPI模式(Quad Peripheral Interface),所有階段都通過4線傳輸。與之相對的是SPI。
- SPI模式:
- 純種SPI(MISO/MOSI兩個數據線)
- DOUT 全稱 Dual I/O,命令字和地址字均爲單線,僅在數據階段爲雙線。
- QOUT 全稱 Quad I/O,命令字和地址字均爲單線,僅在數據階段爲雙線。
- DIO 全稱 Dual I/O (DQ0,DQ1兩個數據線),首個命令字爲單線傳輸,後面地址和數據爲雙線。
- QIO 全稱 Quad I/O (DQ0~DQ3四個數據線),首個命令字爲單線傳輸,後面地址和數據爲四線。
以上的階段指的是 Command-Address-Data三階段,Command只佔用一個字節。
常見FLASH芯片詳解
MT25QU512
芯片手冊:https://www.mouser.com/datasheet/2/671/mict_s_a0004996076_1-2290890.pdf
國產芯片:IS25WP512M QPI https://www.mouser.cn/datasheet/2/198/25LP_WP512M-1221196.pdf
QPI(Quad Peripheral Interface) 採用DQ0傳輸指令,地址和數據則通過DQ0~3傳輸。
MT25QU512ABB8E12-0SIT
我用的那款連線屬於8E系,使用Qual SPI模式(複用功能略有不同,參考手冊Figure5即可):
- S# 接上拉電阻+SPI片選引腳
- C 時鐘線
- DQ0
- DQ1
- DQ2/W# 寫保護
- DQ3/HOLD#
- RESET# 復位,QIO-SPI模式下不可用。
FLASH內部架構框圖
- 控制線連接到控制邏輯Control logic,用於整體的狀態操作,類似於FPGA的全局開關Flag。全程狀態寄存器用於指示閃存模塊Memory就緒等狀態。
- DQ連接到I/O移位寄存器(這個是SPI原理),然後根據DQ內容執行以下分支:
- 如果DQ是地址,那麼通過地址寄存器和計數器用於尋址,映射到閃存模塊Memory上的X,Y二維地址矩陣。
- 如果DQ是數據,那麼則Buffer模塊作爲緩衝,用於從閃存模塊Memory儲存上讀寫數據後置於移位寄存器,這涉及串並轉換。
- 如果DQ是OTP,則對應到"64 OTP bytes"模塊,執行對應的指令。OTP(全稱ONE-TIME PROGRAMMABLE)這種只能寫入一次的空間常用於放置加密密鑰或者唯一ID。
- 如果是狀態指令,可對兩個8位的狀態寄存器進行讀寫。狀態寄存器TOP BP3~BP0可以組合指示FLASH只讀保護區
注: DQ0~DQ3 是雙向複用SPI數據線DIO/QIO,用於傳輸FLASH的指令數據地址信息。以下是各個DQ線的傳輸方向:
其中Memory的儲存映射分佈爲
E系列是Sector父扇區(64KB) - Subsector子扇區32KB - Subsector孫扇區4KB,然後纔是對孫扇區每個字節的尋址
也就是:必須找到孫扇區編號後,才能尋址。
關於JEDEC標準
寄存器
Nonvolatile Configuration Register
這裏關鍵可以配置
- 協議模式,如Quad/Dual
- 地址模式4-byte或3-byte address mode
- XIP速度模式
- 時鐘週期(Number of dummy clock cycles),週期數必須和時鐘頻率一致,否則儲存器讀取到的數據不正確
I/O protocol
Quad IO 協議,是指令-地址-數據 4-4-4 模式
Dual IO協議,是指令-地址-數據 2-2-2模式
第二條提到:一般來說,模式前的數字代表這部分使用了多少DQ線,如2-4-4代表 傳輸command部分時使用2根DQ,傳輸Address部分時使用4根DQ,傳輸Data部分時使用4根DQ。
時序:
當然我們不需要那麼多種模式,我們使用Quad SPI的時候,只需要關注4-4-4, 4-0-4, 4-0-0, 4-4-0這幾種即可。
3/4字節地址模式
另外還需要特別關注的是 3-Bytes 和 4-Bytes地址模式的區別。
- 4Bytes地址模式會直接使用Address寄存器,地址尋址爲A[31:0]
- 3Bytes地址模式,僅僅支持128MB儲存,它有個Extened Address寄存器,低3位表示儲存所在的段序號,高5位表示地址,組成地址尋址A[31:24]。
內部配置寄存器(Internal Configuration Register)
內部配置寄存器Internal Configuration 由易失和非易失組成(Nonvolatile configuration和Volatile configuration + enhanced volatile configuration)
非易失寄存器提供的配置項:
- 4/3字節地址模式
- 3字節地址模式所選的128MB段,
- Quad I/O 協議開關
- Dual I/O 協議開關
- DTR(Double transfer rate) 協議開關
- 電流驅動力
- XIP模式:Fast/Dual/Quad等等
- 時鐘週期(Number of dummy clock cycles),週期數必須和時鐘頻率一致,否則儲存器讀取到的數據不正確
注:DTR是雙邊沿觸發的意思,比與之相對的STR單邊沿觸發快一倍。
易失寄存器提供的功能配置項:
- 時鐘週期(Number of dummy clock cycles),週期數必須和時鐘頻率一致,否則儲存器讀取到的數據不正確
- XIP開關
- Wrap 填充 用於N字節對齊(連續不需對齊/64B/32B/16B對齊),也和字節順序有關,效果如下表
增強易失寄存器提供的功能配置項: - Quad I/O協議開關
- Dual I/O協議開關
- DTR 協議開關
- Reset/Hold 開關位
- 電流驅動能力
其中時鐘週期配置根據這個表
其作用在地址和數據之間的間隔
安全寄存器
Security Register可配置同時鎖住第n個扇區(0 ~ 15) 並設置密碼
寫入操作/PROGRAM
由於FLASH介質問題,而且Flash芯片沒有像EMMC那麼高端,寫入操作和常見的磁盤並不一樣。所以寫入的命令也不叫Write,而是叫PROGRAM
- 讀取STATUS寄存器查看BUSY位,只有芯片不BUSY的時候才能繼續下面的操作
- 發送Write Enable指令
- 發送PROGRAM指令
- FLASH芯片會自動回到Write Disabled狀態
常用指令
Device ID Data
這部分一般是JEDEC規範+廠家自己的規範,這個ID用於識別廠商型號等等,底層Bootload到UBoot再到Kernel,都會使用這個ID來匹配啓用對應的從設備FLASH驅動。
CRC
多項式:
用於對比讀/寫的數據正確性。
STATUS Table
更多具體請查閱數據手冊,在此僅拋磚引玉。
Linux驅動部分
SPI驅動
請見我的另一篇文章:Linux內核之SPI協議
配置驅動
我們這裏選用 Qual SPI來操作Flash。
ZYNQMP 使用 QSPI 的官方指南文檔:https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841754/Zynqmp+QSPI+Driver
設備樹配置大概是這樣:
這裏會用到兩個關鍵驅動 compatible = "xlnx,zynqmp-qspi-1.0";
和 compatible = "n25q512a", "jedec,spi-nor";
QSPI控制器驅動源碼
這部分的驅動只處理字節流
drivers/spi/spi-zynqmp-gqspi.c
關鍵probe在下圖1358行,它使用了spi_controller結構體(SoC片上的SPI控制器實例)
主要的操作是:
- 分配spi_controller控制器實例
- 配置platform驅動私有數據pdev
- 設備樹匹配和處理
- plarform驅動的資源ioremap (寄存器映射)
- 時鐘配置
- 電源管理pm_runtime_開頭的API
- SPI模式配置(相位、極性、Dual/Quad模式、速率、片選等)
- QSPI硬件控制器初始化
- 中斷處理
- DMA掩碼配置
- fops指針賦值(zynqmp_qspi_mem_ops、zynqmp_qspi_setup_op)
zynqmp_qspi_mem_ops
僅僅是開啓SoC片上的QSPI控制器而已。zynqmp_qspi_mem_ops
裏面只有一個.exec_op成員,指向了zynqmp_qspi_exec_op
,主要是初始化QSPI傳輸所需的底層實現,主要是QSPI協議的實現,如Command-Addr-Data模式配置,opcode、將TX/RX BUF和SOC QSPI寄存器上的收發寄存器交互等等。
- devm_spi_register_controller
- 啓動pm_runtime
Tips: 大量使用了devm_
開頭的函數,這是Linux內核提供給開發者的設備相關的自動管理API,其生命週期(堆棧內存)會根據設備模型自動維護。詳見:Devres - Managed Device Resource
NOR FLASH 驅動源碼
內核NOR框架文檔:SPI NOR framework
驅動源碼位於 drivers/mtd/spi-nor/core.c
probe函數體
static int spi_nor_probe(struct spi_mem *spimem)
{
struct spi_nor_flash_parameter *params;
struct spi_device *spi = spimem->spi;
struct flash_platform_data *data = dev_get_platdata(&spi->dev);
struct spi_nor *nor;
/*
* Enable all caps by default. The core will mask them after
* checking what's really supported using spi_mem_supports_op().
*/
const struct spi_nor_hwcaps hwcaps = { .mask = SNOR_HWCAPS_ALL };
char *flash_name;
int ret;
nor = devm_kzalloc(&spi->dev, sizeof(*nor), GFP_KERNEL);
if (!nor)
return -ENOMEM;
nor->spimem = spimem;
nor->dev = &spi->dev;
spi_nor_set_flash_node(nor, spi->dev.of_node);
if (nor->spimem)
init_completion(&nor->spimem->request_completion);
spi_mem_set_drvdata(spimem, nor);
if (data && data->name)
nor->mtd.name = data->name;
if (!nor->mtd.name)
nor->mtd.name = spi_mem_get_name(spimem);
/*
* For some (historical?) reason many platforms provide two different
* names in flash_platform_data: "name" and "type". Quite often name is
* set to "m25p80" and then "type" provides a real chip name.
* If that's the case, respect "type" and ignore a "name".
*/
if (data && data->type)
flash_name = data->type;
else if (!strcmp(spi->modalias, "spi-nor"))
flash_name = NULL; /* auto-detect */
else
flash_name = spi->modalias;
ret = spi_nor_scan(nor, flash_name, &hwcaps);
if (ret)
return ret;
spi_nor_debugfs_register(nor);
params = spi_nor_get_params(nor, 0);
/*
* None of the existing parts have > 512B pages, but let's play safe
* and add this logic so that if anyone ever adds support for such
* a NOR we don't end up with buffer overflows.
*/
if (params->page_size > PAGE_SIZE) {
nor->bouncebuf_size = params->page_size;
devm_kfree(nor->dev, nor->bouncebuf);
nor->bouncebuf = devm_kmalloc(nor->dev,
nor->bouncebuf_size,
GFP_KERNEL);
if (!nor->bouncebuf)
return -ENOMEM;
}
ret = spi_nor_create_read_dirmap(nor);
if (ret)
return ret;
ret = spi_nor_create_write_dirmap(nor);
if (ret)
return ret;
return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
data ? data->nr_parts : 0);
}
主要操作有:
- 配置支持的特性
- 分配內存並賦值
- 私有數據
- name處理
- 通過spi_nor_scan掃描總線
- debugfs註冊
- 從FLASH硬件獲取參數
- 創建讀寫所需的dirmap
- 註冊mtd設備
。。。未完待續