4. SPI 硬件接口:典型 SPI 控制器寄存器映射、FIFO 与 DMA 支持、中断处理流程
SPI(Serial Peripheral Interface)作为网关设备中最常用的板级通信总线之一,其硬件接口的寄存器映射、FIFO/DMA 数据通路以及中断处理机制,直接决定了驱动层的适配复杂度与传输性能。本节从硬件寄存器视图出发,逐步深入到数据流加速与中断响应流程,帮助开发者建立从“寄存器位”到“中断服务函数”的完整认知。
4.1 典型 SPI 控制器寄存器映射
主流 SoC 中的 SPI 控制器(如 STM32 SPI、NXP FlexSPI、TI McSPI)在寄存器布局上具有高度相似性。下表归纳了核心寄存器组及其功能分类:
| 寄存器名称 | 偏移地址 | 功能描述 | 关键位域 |
|---|---|---|---|
| SPI_CR1 | 0x00 | 控制寄存器 1:主从模式、波特率、CPOL/CPHA、使能 | MSTR, BR[2:0], CPOL, CPHA, SPE |
| SPI_CR2 | 0x04 | 控制寄存器 2:中断使能、DMA 使能、SS 输出控制 | TXEIE, RXNEIE, ERRIE, TXDMAEN, RXDMAEN |
| SPI_SR | 0x08 | 状态寄存器:发送空、接收非空、忙标志、溢出错误 | TXE, RXNE, BSY, OVR, MODF |
| SPI_DR | 0x0C | 数据寄存器:8/16/32 位收发数据缓冲 | DR[15:0] 或 DR[31:0] |
| SPI_CRCPR | 0x10 | CRC 多项式寄存器 | CRCPOLY[7:0] |
| SPI_TXCRCR | 0x14 | 发送 CRC 值(只读) | TxCRC[7:0] |
| SPI_RXCRCR | 0x18 | 接收 CRC 值(只读) | RxCRC[7:0] |
| SPI_I2SCFGR | 0x1C | I2S 模式配置(可选) | I2SMOD, I2SE |
struct spi_master 的 transfer_one() 回调直接操作 SPI_DR 寄存器。寄存器映射通常由 devm_platform_ioremap_resource() 完成,基地址来自设备树 reg 属性。
以下是一个典型的 SPI 控制器寄存器基地址映射代码片段(以 STM32MP1 为例):
/* 设备树中定义的 SPI 控制器基地址 */
#define STM32_SPI1_BASE 0x44004000
/* 寄存器偏移宏定义 */
#define SPI_CR1_OFFSET 0x00
#define SPI_CR2_OFFSET 0x04
#define SPI_SR_OFFSET 0x08
#define SPI_DR_OFFSET 0x0C
/* 驱动中映射寄存器 */
static int stm32_spi_probe(struct platform_device *pdev)
{
struct stm32_spi *spi;
struct resource *res;
spi = devm_kzalloc(&pdev->dev, sizeof(*spi), GFP_KERNEL);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
spi->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(spi->base))
return PTR_ERR(spi->base);
/* 初始化控制寄存器:主模式,8 位数据,极性/相位由设备树决定 */
writel_relaxed(SPI_CR1_MSTR | SPI_CR1_SSI | SPI_CR1_SSM,
spi->base + SPI_CR1_OFFSET);
return 0;
}
- 使用
readl_relaxed()/writel_relaxed()避免不必要的内存屏障开销。 - 在修改
SPI_CR1的SPE位之前,必须先配置好 CPOL/CPHA 和波特率,否则可能导致总线状态异常。 - 状态寄存器
SPI_SR的BSY位在轮询模式下是判断传输结束的关键标志。
4.2 FIFO 与 DMA 支持
现代 SPI 控制器普遍内置深度可配置的 FIFO(通常为 8~32 级),并支持 DMA 请求线,以降低 CPU 中断频率并提升吞吐量。FIFO 与 DMA 的协同工作模式是网关高速 SPI 通信(如 SPI Flash、ADC 采集)的性能基石。
4.2.1 FIFO 深度与阈值配置
FIFO 的阈值(Threshold)决定了何时触发中断或 DMA 请求。典型配置如下表:
| FIFO 深度 | 发送阈值(TXFT) | 接收阈值(RXFT) | 适用场景 |
|---|---|---|---|
| 8 级 | FIFO 空(< 1 数据项) | FIFO 非空(≥ 1 数据项) | 低延迟、小数据包 |
| 16 级 | FIFO 半空(< 8 数据项) | FIFO 半满(≥ 8 数据项) | 平衡延迟与吞吐量 |
| 32 级 | FIFO 几乎空(< 24 数据项) | FIFO 几乎满(≥ 24 数据项) | 高吞吐、批量传输 |
spi_device.max_speed_hz 动态计算阈值。
4.2.2 DMA 请求与数据流
SPI 控制器通常提供独立的 TX DMA 请求线和 RX DMA 请求线。当 FIFO 满足阈值条件时,硬件自动拉高 DMA 请求信号,DMA 控制器响应后执行内存到外设(TX)或外设到内存(RX)的数据搬运。
在 Linux 内核中,SPI 驱动的 DMA 支持通过 struct spi_master 的 can_dma 和 max_dma_len 字段声明。典型的 DMA 传输设置流程如下:
/* SPI 控制器驱动中注册 DMA 支持 */
static int spi_master_probe_dma(struct platform_device *pdev,
struct spi_master *master)
{
struct dma_slave_config tx_config, rx_config;
/* 配置 TX DMA 通道 */
master->dma_tx = dma_request_chan(&pdev->dev, "tx");
if (IS_ERR(master->dma_tx))
return dev_err_probe(&pdev->dev, PTR_ERR(master->dma_tx),
"Failed to request TX DMA\n");
tx_config.direction = DMA_MEM_TO_DEV;
tx_config.dst_addr = res->start + SPI_DR_OFFSET;
tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmaengine_slave_config(master->dma_tx, &tx_config);
/* 配置 RX DMA 通道 */
master->dma_rx = dma_request_chan(&pdev->dev, "rx");
rx_config.direction = DMA_DEV_TO_MEM;
rx_config.src_addr = res->start + SPI_DR_OFFSET;
rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
dmaengine_slave_config(master->dma_rx, &rx_config);
master->can_dma = stm32_spi_can_dma;
master->max_dma_len = SZ_4K; /* 单次 DMA 最大传输长度 */
return 0;
}
4.3 中断处理流程
SPI 中断处理是驱动实时性的关键路径。典型的中断源包括:发送 FIFO 空(TXE)、接收 FIFO 非空(RXNE)、错误(OVR、MODF)以及 DMA 传输完成。中断处理流程需要兼顾数据吞吐与错误恢复。
4.3.1 中断类型与优先级
| 中断标志 | 触发条件 | 典型处理动作 | 优先级建议 |
|---|---|---|---|
| TXE | TX FIFO 空(低于阈值) | 从发送缓冲区填充数据到 SPI_DR | 中 |
| RXNE | RX FIFO 非空(达到阈值) | 从 SPI_DR 读取数据到接收缓冲区 | 中 |
| OVR | 接收溢出(RX FIFO 满且新数据到达) | 清除 OVR 位,丢弃旧数据,记录错误 | 高 |
| MODF | 模式错误(多主冲突) | 复位 SPI 控制器,重新配置主模式 | 最高 |
| DMA 完成 | DMA 传输完成(TC 事件) | 调用回调,唤醒等待队列 | 低 |
4.3.2 中断服务函数典型实现
以下是一个基于 FIFO 中断的 SPI 中断服务函数(ISR)示例,展示了如何高效处理 TXE/RXNE 事件:
static irqreturn_t stm32_spi_irq(int irq, void *dev_id)
{
struct stm32_spi *spi = dev_id;
u32 sr = readl_relaxed(spi->base + SPI_SR_OFFSET);
u32 cr2 = readl_relaxed(spi->base + SPI_CR2_OFFSET);
/* 1. 处理接收中断:RXNE 置位且 RXNEIE 使能 */
if ((sr & SPI_SR_RXNE) && (cr2 & SPI_CR2_RXNEIE)) {
u32 rx_data = readl_relaxed(spi->base + SPI_DR_OFFSET);
if (spi->rx_buf && spi->rx_len > 0) {
*spi->rx_buf++ = (u8)rx_data;
spi->rx_len--;
}
}
/* 2. 处理发送中断:TXE 置位且 TXEIE 使能 */
if ((sr & SPI_SR_TXE) && (cr2 & SPI_CR2_TXEIE)) {
if (spi->tx_buf && spi->tx_len > 0) {
writel_relaxed(*spi->tx_buf++, spi->base + SPI_DR_OFFSET);
spi->tx_len--;
} else {
/* 发送完成,关闭 TXEIE 中断 */
writel_relaxed(cr2 & ~SPI_CR2_TXEIE, spi->base + SPI_CR2_OFFSET);
}
}
/* 3. 处理错误中断 */
if (sr & (SPI_SR_OVR | SPI_SR_MODF)) {
dev_err(spi->dev, "SPI error: SR=0x%08x\n", sr);
/* 清除错误标志:读 SR 后读 DR */
readl_relaxed(spi->base + SPI_DR_OFFSET);
/* 触发错误回调 */
if (spi->xfer_complete)
complete(&spi->xfer_done);
}
/* 4. 传输完成判断:TX 和 RX 都结束 */
if (spi->tx_len == 0 && spi->rx_len == 0) {
/* 关闭所有中断 */
writel_relaxed(0, spi->base + SPI_CR2_OFFSET);
complete(&spi->xfer_done);
}
return IRQ_HANDLED;
}
- 快速路径:ISR 中只做寄存器读写和缓冲区指针移动,不调用
memcpy或kmalloc。 - 错误恢复:MODF 错误需要完全复位 SPI 控制器,建议在 ISR 中设置标志,由工作队列执行复位。
- DMA 协同:当 DMA 使能时,ISR 应关闭 TXE/RXNE 中断,仅保留错误中断和 DMA 完成回调。
4.3.3 中断与 DMA 的切换策略
实际驱动中,中断模式和 DMA 模式根据传输长度动态切换。以下是一个典型的决策流程:
4.4 小结
本节从寄存器映射、FIFO/DMA 数据通路、中断处理三个维度剖析了 SPI 硬件接口的适配要点。开发者需要重点掌握:寄存器位域的正确配置顺序、FIFO 阈值与 DMA 请求的协同关系、以及中断服务函数中快速路径与错误恢复的平衡。这些硬件细节将直接决定后续总线抽象层设计的健壮性与性能上限。