Linux內核源碼-存儲驅動之 QSPI Flash

傳輸方式 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內部架構框圖
框圖

  1. 控制線連接到控制邏輯Control logic,用於整體的狀態操作,類似於FPGA的全局開關Flag。全程狀態寄存器用於指示閃存模塊Memory就緒等狀態。
  2. DQ連接到I/O移位寄存器(這個是SPI原理),然後根據DQ內容執行以下分支:
  3. 如果DQ是地址,那麼通過地址寄存器和計數器用於尋址,映射到閃存模塊Memory上的X,Y二維地址矩陣。
  4. 如果DQ是數據,那麼則Buffer模塊作爲緩衝,用於從閃存模塊Memory儲存上讀寫數據後置於移位寄存器,這涉及串並轉換。
  5. 如果DQ是OTP,則對應到"64 OTP bytes"模塊,執行對應的指令。OTP(全稱ONE-TIME PROGRAMMABLE)這種只能寫入一次的空間常用於放置加密密鑰或者唯一ID。
  6. 如果是狀態指令,可對兩個8位的狀態寄存器進行讀寫。狀態寄存器TOP BP3~BP0可以組合指示FLASH只讀保護區

注: DQ0~DQ3 是雙向複用SPI數據線DIO/QIO,用於傳輸FLASH的指令數據地址信息。以下是各個DQ線的傳輸方向:
DQ功能備註表

其中Memory的儲存映射分佈爲
image

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模式
image
image
image
image
image

Note
第二條提到:一般來說,模式前的數字代表這部分使用了多少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)
image

非易失寄存器提供的配置項:

  • 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對齊),也和字節順序有關,效果如下表
    image
    增強易失寄存器提供的功能配置項:
  • Quad I/O協議開關
  • Dual I/O協議開關
  • DTR 協議開關
  • Reset/Hold 開關位
  • 電流驅動能力

其中時鐘週期配置根據這個表
CLk_STR_in_IT
CLK_DTR

其作用在地址和數據之間的間隔
FAST_READ

安全寄存器

Security Register可配置同時鎖住第n個扇區(0 ~ 15) 並設置密碼

寫入操作/PROGRAM

由於FLASH介質問題,而且Flash芯片沒有像EMMC那麼高端,寫入操作和常見的磁盤並不一樣。所以寫入的命令也不叫Write,而是叫PROGRAM

  1. 讀取STATUS寄存器查看BUSY位,只有芯片不BUSY的時候才能繼續下面的操作
  2. 發送Write Enable指令
  3. 發送PROGRAM指令
  4. FLASH芯片會自動回到Write Disabled狀態

PROGRAM
QUAD_PROGRAM

常用指令

Device ID Data

這部分一般是JEDEC規範+廠家自己的規範,這個ID用於識別廠商型號等等,底層Bootload到UBoot再到Kernel,都會使用這個ID來匹配啓用對應的從設備FLASH驅動。

CRC

image
多項式:
image
用於對比讀/寫的數據正確性。

STATUS Table

image

更多具體請查閱數據手冊,在此僅拋磚引玉。

Linux驅動部分

SPI驅動

請見我的另一篇文章:Linux內核之SPI協議

配置驅動

我們這裏選用 Qual SPI來操作Flash。

ZYNQMP 使用 QSPI 的官方指南文檔:https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841754/Zynqmp+QSPI+Driver
設備樹配置大概是這樣:
image

這裏會用到兩個關鍵驅動 compatible = "xlnx,zynqmp-qspi-1.0";compatible = "n25q512a", "jedec,spi-nor";

QSPI控制器驅動源碼

這部分的驅動只處理字節流

drivers/spi/spi-zynqmp-gqspi.c

image

關鍵probe在下圖1358行,它使用了spi_controller結構體(SoC片上的SPI控制器實例)
image

image

image

主要的操作是:

  1. 分配spi_controller控制器實例
  2. 配置platform驅動私有數據pdev
  3. 設備樹匹配和處理
  4. plarform驅動的資源ioremap (寄存器映射)
  5. 時鐘配置
  6. 電源管理pm_runtime_開頭的API
  7. SPI模式配置(相位、極性、Dual/Quad模式、速率、片選等)
  8. QSPI硬件控制器初始化
  9. 中斷處理
  10. DMA掩碼配置
  11. 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寄存器上的收發寄存器交互等等。
  1. devm_spi_register_controller
  2. 啓動pm_runtime

Tips: 大量使用了devm_開頭的函數,這是Linux內核提供給開發者的設備相關的自動管理API,其生命週期(堆棧內存)會根據設備模型自動維護。詳見:Devres - Managed Device Resource

NOR FLASH 驅動源碼

內核NOR框架文檔:SPI NOR framework

驅動源碼位於 drivers/mtd/spi-nor/core.c

image

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

主要操作有:

  1. 配置支持的特性
  2. 分配內存並賦值
  3. 私有數據
  4. name處理
  5. 通過spi_nor_scan掃描總線
  6. debugfs註冊
  7. 從FLASH硬件獲取參數
  8. 創建讀寫所需的dirmap
  9. 註冊mtd設備

。。。未完待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章