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设备

。。。未完待续

posted @ 2024-04-29 17:38  蓝天上的云℡  阅读(658)  评论(0编辑  收藏  举报