Linux SPI/QSPI nor flash相关驱动代码

本文详细介绍了SPI与QSPI Flash在Linux内核中的驱动实现方式,包括SPI控制器及Flash设备的驱动结构、文件关系及其如何通过mtd子系统进行读写擦操作。同时,也探讨了平台总线机制在驱动加载过程中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于 spi 接口,本身是可以接很多种类的外设的,比如用于接带有SPI通信协议的芯片,通常是将其作为字符设备类型注册。

对于接SPI/QSPI Flash时,又通常作为mtd设备(memory technology device内存技术设备)。MTD的主要目的是为了使新的memory设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口,主要是读写擦接口。

在芯片厂家设计接口时,有时会把接SPI/QSPI flash接口的单独封装成独立于SPI的专用接口,这就会出现两种情况外设接的是Flash。造就了在SPI 控制器(master)侧的驱动放的位置不一样。

1. 通用SPI 接口,通常通过drivers/mtd/devices/m25p80.c 注册mtd设备

2.SPI/QSPI flash 专用接口控制器

通用SPI 接口文件关系

参考:https://blog.youkuaiyun.com/kickxxx/article/details/68924170

Linux kernel中QSPI nor flash相关驱动文件:

1. drivers/spi/spi.c: 是linux spi通用框架代码, 向下适配ti mcspi以及ti qspi控制器驱动.
2. drivers/spi/spi-ti-qspi.c:  TI qspi主控器驱动, 不同平台使用不同的主控驱动
3. drivers/mtd/devices/m25p80.c: m25p80.c和spi-nor.c都是linux spi nor驱动框架的一部分
4. drivers/mtd/spi-nor/spi-nor.c: m25p80.c和spi-nor.c都是linux spi nor驱动框架的一部分

SPI/QSPI flash 专用接口文件关系

1. drivers/mtd/spi-nor/fsl-quadspi.c: fsl qspi专用flash主控制器驱动,不同平台使用不同的主控制器驱动。mtd设备注册也是在这主流程中。

2. drivers/mtd/spi-nor/spi-nor.c: 提供通用扫描接口,并封装向上mtd层提供的spi-nor的读写擦接口

系统框架

mtd :  mtd->_read,

spi-nor: spi_nor_read

控制器:nor->read = fsl_qspi_read

spi-nor.c

从架构图可以看出spi-nor是简单的粘合层, 把mtd调用转换为m25p80接口.  spi-nor还包含了qspi芯片检测, 通过芯片ID, 确定spi nor的参数:sector size, nsectors, pagesize, flags.

spi-nor驱动适配多种spi nor flash, 这些flash的驱动层代码差异, 由flash_info的几个参数表示.

spi-nor.c为mtd子系统服务, 为mtd层提供如下几个接口:

  • spi_nor_write
  • spi_nor_erase
  • spi_nor_read

spi_nor_scan调用spi_nor_read_id从chip获取ID, 根据chip ID得到该芯片的flash_info

1. 设置mtd_info的type, writesize, size等参数

2. 设置mtd_info的_erase, _read, _write回调函数

3. 设置mtd_info 的erasesize    

驱动分析

一个现实的设备通常都需要挂接在一种总线上,比较常见的总线有USBPCI总线等。但是,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。基于这样的背景下,2.6内核加入了platform虚拟总线。platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源时使用统一的接口platform总线对加入到该总线的设备和驱动分别封装了两个结构体——platform_deviceplatform_driver。并且提供了对应的注册函数

SPI控制器依附platform bus,驱动采用platform driver描述。

SPI 设备(SPI Flash)依附SPI 总线,是实体存在的总线机制,对应驱动采用spi driver描述。


Platform总线、设备、 驱动

platform总线注册由内核管理,平台设备platform_device描述的SPI控制器的信息(数据),platform_driver对应提供控制器的使用方法(策略)支持。

之前的内核platform_device描述一般如下

static struct platform_device qt2410_cs89x0 = {
    .name        = "cirrus-cs89x0",
    .num_resources    = ARRAY_SIZE(qt2410_cs89x0_resources),
    .resource    = qt2410_cs89x0_resources,

};

通过以下两个函数中之一注册设备

int platform_device_register(struct platform_device *);
int platform_add_devices(struct platform_device **, int); //针对的是1个或多个

支持dts的内核版本,platform_device描述直接在dts中描述,有内核解析dts时统一生成platform_device

在初始化先后顺序上,从左到右是:平台总线 --》平台设备 --》平台驱动。实际上所有的总线类型,平台总线总是要较早初始化的。






<think>我们正在讨论SPIQSPI在嵌入式开发中代码实现的区别。根据引用资料,SPI是标准的四线制(SCLK, MOSI, MISO, CS)全双工通信协议,而QSPI则增加了数据线(通常为4根数据线)并支持队列机制,通常用于高速访问闪存,且QSPI可以是半双工(在4线模式下)或全双工(在2线模式下,但通常4线模式用于提高吞吐量)。 在代码层面,主要区别体现在以下几个方面: 1. **初始化配置**: - SPI:通常配置为主机模式、时钟极性、时钟相位、数据位顺序(MSB/LSB)、时钟频率等。数据线只有一根输入(MISO)和一根输出(MOSI)。 - QSPI:除了配置时钟等基本参数外,还需要配置数据线宽度(1线、2线、4线模式)、指令模式(如指令、地址、数据阶段分别使用几线传输)、内存映射模式等。QSPI控制器通常支持更复杂的操作序列(如命令、地址、交替字节、数据等阶段)。 2. **数据传输**: - SPI:每次传输通常以字节为单位,通过写入数据寄存器并等待传输完成标志(或使用中断/DMA)来发送和接收数据。全双工模式下,发送和接收同时进行。 - QSPI:支持单线、双线、四线模式传输。在四线模式下,每个时钟周期可以传输4位数据,因此传输同样数据量所需时间更短。此外,QSPI通常支持命令序列(Command Sequence),可以一次性设置好整个操作(包括指令、地址、数据等)然后启动传输。同时,QSPI还支持内存映射模式,使得外部闪存可以像内部存储器一样直接读取(通过地址访问),而不需要发送命令。 3. **操作模式**: - SPI:通常需要软件控制每个操作步骤(发送命令、地址、数据等),需要多个SPI传输过程。 - QSPI:支持自动生成操作序列。例如,读取闪存数据时,可以配置一个间接模式(Indirect Mode)的传输序列,包含命令、地址和读取数据阶段,然后启动传输,硬件会自动完成整个序列。在内存映射模式(Memory Mapped Mode)下,CPU可以直接通过地址读取数据,无需软件干预。 4. **性能优化**: - QSPI通常支持DMA,可以减轻CPU负担。同时,由于队列机制(Queued),可以连续发送多个命令而无需等待每个命令完成(但需要硬件支持队列)。 下面以读取闪存ID为例,对比SPIQSPI代码实现: **SPI读取闪存ID(以标准SPI为例)**: ```c // 假设SPI初始化已经完成,片选信号由软件控制 // 1. 拉低片选 CS_LOW(); // 2. 发送读取ID命令(0x9F) spi_send(0x9F); // 发送命令 // 3. 接收3个字节的ID(制造商ID、存储器类型、容量) uint8_t manufacturer_id = spi_receive(); uint8_t memory_type = spi_receive(); uint8_t capacity = spi_receive(); // 4. 拉高片选 CS_HIGH(); ``` **QSPI读取闪存ID(间接模式)**: ```c // 配置QSPI命令序列 QSPI_CommandTypeDef cmd; cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; // 指令使用1线传输 cmd.Instruction = 0x9F; // 读取ID命令 cmd.AddressMode = QSPI_ADDRESS_NONE; // 无地址 cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 无交替字节 cmd.DataMode = QSPI_DATA_1_LINE; // 数据阶段使用1线(也可以使用4线,但需要闪存支持) cmd.DummyCycles = 0; // 无空周期 cmd.NbData = 3; // 接收3个字节 cmd.DdrMode = QSPI_DDR_MODE_DISABLE; // 禁止DDR模式 cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; // 每次传输都发送指令 // 配置QSPI为间接模式,发送命令 HAL_QSPI_Command(&hqspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); // 接收数据 uint8_t id[3]; HAL_QSPI_Receive(&hqspi, id, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); ``` 或者,在支持内存映射模式的情况下,读取闪存ID可能不需要软件显式发送命令,因为闪存被映射到内存地址空间,但读取ID操作通常还是需要使用间接模式(因为内存映射模式通常用于读取数据,而不是发送命令)。 **总结**: - SPI代码实现较为简单,但需要软件控制每个步骤,且传输效率较低。 - QSPI代码实现更复杂,需要配置命令序列,但传输效率高(尤其在4线模式下),并且支持内存映射模式,使得读取操作更加高效(像访问内存一样)。 在嵌入式开发中,使用QSPI通常需要硬件支持(MCU内置QSPI控制器)和相应的驱动(如STM32的HAL库提供QSPI接口函数)。对于连接SPI Flash,如果对性能要求高,推荐使用QSPI。 §§相关问题§§ 1. QSPI的内存映射模式如何配置? 2. 在QSPI的四线模式下,如何确保数据正确性? 3. SPIQSPI在功耗方面有什么区别? 4. 如何选择SPIQSPI用于不同的外设?</think>### SPIQSPI协议在嵌入式开发中的代码实现区别 #### 1. **初始化配置** - **SPI**: 需配置时钟极性(CPOL)、时钟相位(CPHA)、主从模式、数据位宽(通常8位)和时钟频率。 示例代码(STM32 HAL库): ```c SPI_HandleTypeDef hspi; hspi.Instance = SPI1; hspi.Init.Mode = SPI_MODE_MASTER; // 主模式 hspi.Init.Direction = SPI_DIRECTION_2LINES; // 全双工 hspi.Init.DataSize = SPI_DATASIZE_8BIT; // 8位数据 hspi.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0 hspi.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0 hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 时钟分频 HAL_SPI_Init(&hspi); // 初始化 ``` - **QSPI**: 需额外配置数据线宽度(1/2/4线)、指令模式、闪存地址空间等。支持**内存映射模式**(直接访问外部闪存如内部内存)。 示例代码(STM32 HAL库): ```c QSPI_HandleTypeDef hqspi; hqspi.Instance = QUADSPI; hqspi.Init.ClockPrescaler = 1; // 更高时钟频率 hqspi.Init.FifoThreshold = 4; // 4线数据传输 hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; hqspi.Init.FlashSize = 24; // 24位地址(16MB空间) HAL_QSPI_Init(&hqspi); // 初始化 // 配置内存映射模式(直接读取闪存) HAL_QSPI_EnableMemoryMappedMode(&hqspi); ``` #### 2. **数据传输** - **SPI**: 每次传输1位数据(全双工),需逐字节操作。 发送/接收示例: ```c uint8_t tx_data = 0xAA, rx_data; HAL_SPI_TransmitReceive(&hspi, &tx_data, &rx_data, 1, HAL_MAX_DELAY); ``` - **QSPI**: 支持单周期传输多位数据(如4位),通过**命令序列**(Command-Sequence)整合指令、地址和数据阶段。 四线模式读取示例: ```c QSPI_CommandTypeDef cmd; cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; // 指令用1线 cmd.Instruction = 0xEB; // 读命令 cmd.AddressMode = QSPI_ADDRESS_4_LINES; // 地址用4线 cmd.AddressSize = QSPI_ADDRESS_24_BITS; // 24位地址 cmd.DataMode = QSPI_DATA_4_LINES; // 数据用4线 cmd.NbData = 256; // 读取256字节 HAL_QSPI_Command(&hqspi, &cmd, HAL_MAX_DELAY); // 发送命令 HAL_QSPI_Receive(&hqspi, buffer, HAL_MAX_DELAY); // 接收数据 ``` #### 3. **性能优化** - **SPI**: 依赖CPU或DMA搬运数据,高时钟频率下易成瓶颈。 例如:SPI时钟10MHz时,理论速率仅1.25MB/s(8位数据)。 - **QSPI**: **队列机制**和**内存映射**显著提升效率: - 内存映射模式下,CPU可直接读取外部闪存(如`0x90000000`),无需额外代码。 - 4线模式理论速率可达SPI的4倍(相同时钟下)。 示例(直接访问映射内存): ```c uint8_t *flash_addr = (uint8_t*)0x90000000; // QSPI映射地址 uint32_t data = flash_addr[0]; // 直接读取 ``` #### 4. **应用场景代码对比** - **SPI读取传感器**(如温度传感器): ```c // 发送读命令 uint8_t cmd = 0x01; HAL_SPI_Transmit(&hspi, &cmd, 1, HAL_MAX_DELAY); // 接收2字节数据 uint8_t temp_data[2]; HAL_SPI_Receive(&hspi, temp_data, 2, HAL_MAX_DELAY); ``` - **QSPI读取大容量闪存**(如W25Q128): ```c // 配置四线读取序列(无需逐字节操作) QSPI_CommandTypeDef cmd = { ... }; // 包含4线地址/数据 HAL_QSPI_Command(&hqspi, &cmd, HAL_MAX_DELAY); HAL_QSPI_Receive_DMA(&hqspi, large_buffer, 4096); // DMA传输4KB数据 ``` ### 关键区别总结 | **特性** | **SPI** | **QSPI** | |------------------|----------------------------------|------------------------------------| | 数据线 | 2线(MOSI+MISO) | 4线(IO0-IO3) | | 传输效率 | 单周期1位 | 单周期4位(理论速率×4) | | 典型应用 | 传感器、小容量EEPROM | 大容量SPI NOR/NAND Flash | | 内存映射 | 不支持 | 支持(直接地址访问) | | 代码复杂度 | 简单(逐字节操作) | 复杂(需配置命令序列) | | 时钟频率 | 通常≤50MHz | 可达100MHz+(如STM32H7) | > 提示:QSPI在嵌入式Linux中通常配置为**MTD设备**,通过`/dev/mtdX`直接访问,而SPI需通过`spidev`驱动逐字节读写[^1][^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值