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
适配要点:在 Linux 内核中,SPI 控制器驱动通过 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 数据项) 高吞吐、批量传输
常见陷阱:FIFO 阈值设置过低会导致频繁中断,增加 CPU 负载;设置过高则可能引发接收溢出(OVR)错误。建议在驱动初始化阶段根据 spi_device.max_speed_hz 动态计算阈值。

4.2.2 DMA 请求与数据流

SPI 控制器通常提供独立的 TX DMA 请求线和 RX DMA 请求线。当 FIFO 满足阈值条件时,硬件自动拉高 DMA 请求信号,DMA 控制器响应后执行内存到外设(TX)或外设到内存(RX)的数据搬运。

SPI FIFO + DMA 数据流架构 系统内存 TX Buffer / RX Buffer DMA 控制器 通道 0: TX / 通道 1: RX SPI 控制器 TX FIFO / RX FIFO DMA 读 DMA 写 TX DMA 读 RX DMA 写 FIFO 阈值逻辑 TXFT / RXFT 比较器 状态反馈 中断控制器 NVIC / GIC 中断请求 DMA 请求线由 FIFO 阈值 硬件自动触发,无需 CPU 干预

在 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 模式根据传输长度动态切换。以下是一个典型的决策流程:

SPI 传输模式决策流程 传输请求到达 len > DMA 阈值? 是 DMA 模式 配置 DMA 通道 否 中断模式 使能 TXE/RXNE 启动 SPI 传输 等待传输完成 调用完成回调
工程实践建议:在网关产品中,SPI 连接的设备(如 CAN 控制器、以太网 PHY)通常传输长度固定且较小(8~64 字节),建议优先使用中断模式以降低 DMA 通道占用。对于 SPI Flash 等大块传输(>256 字节),再启用 DMA。

4.4 小结

本节从寄存器映射、FIFO/DMA 数据通路、中断处理三个维度剖析了 SPI 硬件接口的适配要点。开发者需要重点掌握:寄存器位域的正确配置顺序、FIFO 阈值与 DMA 请求的协同关系、以及中断服务函数中快速路径与错误恢复的平衡。这些硬件细节将直接决定后续总线抽象层设计的健壮性与性能上限。