4、SPI总线驱动:控制器框架、DMA传输与设备调试

SPI这个接口,做嵌入式的没人不认识。四根线——SCLK、MOSI、MISO、CS,结构简单,速度却不慢。瑞芯微RK平台的SPI控制器,我这些年打交道下来,觉得它最大的特点就是灵活:主从模式可配、时钟极性可调、DMA能接、FIFO深度也够用。

但灵活的另一面,就是坑多。时钟相位搞反了、DMA没对齐、CS脚控制时序不对……这些我都踩过。今天咱们就把SPI驱动从控制器到设备,再到调试手段,一条线捋清楚。

4.1 SPI控制器驱动框架

RK平台的SPI控制器,本质上是一个内存映射的硬件模块。驱动框架分三层:

  • 核心层:Linux SPI core,提供总线注册、设备匹配、数据传输接口
  • 控制器驱动层:操作寄存器,管理FIFO、时钟、中断、DMA
  • 设备驱动层:挂接到SPI总线,实现具体外设的读写协议

我个人习惯先看设备树。RK3588的SPI节点长这样:

&spi2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&spi2m2_cs0 &spi2m2_pins>;
    cs-gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_LOW>;
    max-freq = <50000000>;

    spidev@0 {
        compatible = "rohm,dh2228fv";
        reg = <0>;
        spi-max-frequency = <25000000>;
    };
};

这里有个细节:cs-gpios 用GPIO模拟片选,而不是硬件CS。为什么?因为硬件CS在连续传输时会有片选释放再拉低的问题,很多从设备受不了。我遇到过一款ADC,每次CS拉高再拉低就会复位,用硬件CS根本没法做连续读。换成GPIO模拟,完美解决。

控制器驱动核心流程:

  1. probe阶段:获取时钟、复位、DMA通道、中断号,初始化FIFO阈值
  2. transfer_one:单次传输的底层实现,写FIFO、触发传输、等待完成
  3. prepare_message:设置时钟极性、相位、字节序
  4. unprepare_message:传输结束后的清理

4.2 DMA传输模式

SPI用PIO模式,小数据量没问题。一旦超过64字节,CPU占用率就上去了。DMA模式才是正经做法。

RK的SPI控制器支持两种DMA触发方式:

模式 触发条件 适用场景
FIFO半满触发 TX FIFO空余过半 / RX FIFO数据过半 中等数据量,延迟敏感
FIFO空/满触发 TX FIFO全空 / RX FIFO全满 大数据量,吞吐优先

我建议用半满触发。为什么?因为空/满触发容易导致DMA请求间隔不均匀,尤其在系统负载高时,DMA响应延迟会拉长,SPI时钟上出现毛刺。我在RK3399上调试一个显示驱动时,就因为这个毛刺导致屏幕闪了一下。换成半满触发,问题消失。

DMA配置的关键代码片段:

static int spi_dma_xfer(struct spi_master *master, struct spi_transfer *xfer)
{
    struct dma_async_tx_descriptor *tx_desc, *rx_desc;

    // 配置TX DMA
    tx_desc = dmaengine_prep_slave_sg(master->dma_tx,
                    xfer->tx_sg.sgl, xfer->tx_sg.nents,
                    DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (!tx_desc)
        return -EINVAL;

    // 配置RX DMA
    rx_desc = dmaengine_prep_slave_sg(master->dma_rx,
                    xfer->rx_sg.sgl, xfer->rx_sg.nents,
                    DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (!rx_desc)
        return -EINVAL;

    // 提交并启动
    dmaengine_submit(tx_desc);
    dmaengine_submit(rx_desc);
    dma_async_issue_pending(master->dma_tx);
    dma_async_issue_pending(master->dma_rx);

    return 0;
}

注意:DMA传输的buffer必须cacheline对齐。RK平台L1 cacheline是64字节。如果buffer没对齐,DMA和CPU看到的数据不一致,读出来的全是0xFF。我曾经排查这个问题花了一整天,最后发现是kmalloc没指定GFP_DMA标志。

4.3 SPI设备驱动开发

设备驱动说白了就是注册一个spi_driver,然后在probe里初始化外设。以一款常见的SPI Flash为例:

static const struct spi_device_id flash_ids[] = {
    { "w25q128", 0 },
    { }
};

static struct spi_driver flash_driver = {
    .driver = {
        .name = "spi-flash",
        .of_match_table = flash_of_match,
    },
    .probe = flash_probe,
    .remove = flash_remove,
    .id_table = flash_ids,
};
module_spi_driver(flash_driver);

设备驱动里最核心的是数据传输。我一般封装一个函数:

static int spi_flash_transfer(struct spi_device *spi, u8 *tx, u8 *rx, int len)
{
    struct spi_transfer t = {
        .tx_buf = tx,
        .rx_buf = rx,
        .len = len,
        .speed_hz = spi->max_speed_hz,
        .bits_per_word = 8,
    };
    struct spi_message m;

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    return spi_sync(spi, &m);
}

这里有个坑:spi_sync是同步接口,会在等待传输完成时休眠。如果在中断上下文调用,系统会panic。我早期写驱动时没注意,结果一跑就Oops。后来改成spi_async才解决。

4.4 调试技巧与避坑指南

调试SPI,我有一套固定流程:

  1. 看波形:逻辑分析仪是必须的。先看SCLK频率对不对,再看CS时序是否满足从设备要求
  2. 检查时钟极性:CPOL和CPHA搞反,数据全错。用示波器对比MISO和SCLK的边沿关系
  3. 验证DMA:关掉DMA用PIO试一次,如果PIO正常DMA异常,问题就在DMA配置上
  4. 打印寄存器:在关键路径加dev_dbg,打印SPI控制器的状态寄存器,看FIFO计数和错误标志

我曾经遇到一个奇葩问题:SPI读回来的数据,低字节总是对的,高字节全是0。查了两天,最后发现是字节序配置错了。RK的SPI控制器默认MSB first,但我的从设备要求LSB first。在设备树里加一行spi-tx-bus-width = <1>;没用,得在驱动里调用spi_setup时设置mode位。

还有一个常见问题:CS片选拉低后,第一个SCLK边沿之前需要一段建立时间。有些从设备要求至少100ns。如果时序太紧,从设备不响应。解决办法是在设备树里加spi-cs-setup-delay-ns属性。

嗯,这里要注意:RK平台的SPI控制器,在连续传输多个spi_transfer时,默认会在每个transfer之间释放CS。如果你需要保持CS一直拉低(比如Flash的连续读命令),必须设置spi_transfer->cs_change = 0。

4.5 SPI驱动知识体系总览

下面这张图,是我自己总结的SPI驱动知识结构。每次带新人,我都先让他们看这个:

SPI驱动知识体系 应用层 / 用户态 SPI设备驱动 (spi_driver) SPI核心层 (spi.c) SPI控制器驱动 PIO模式 DMA模式 SPI硬件控制器 调试要点: ① 逻辑分析仪抓波形 → ② 对比CPOL/CPHA → ③ PIO/DMA切换验证 → ④ 寄存器状态打印

这张图把SPI驱动的层次和模式都串起来了。从上到下,应用层通过设备驱动访问SPI核心,核心层调度控制器驱动,控制器驱动再选择PIO或DMA跟硬件交互。调试时,你可以从任意一层切入,但我的经验是:先确认硬件波形,再逐层向上排查。

总结一下SPI调试的黄金法则:

  • 波形是真理,逻辑分析仪比任何打印都管用
  • DMA出问题时,先切PIO排除硬件问题
  • CS时序问题最常见,用GPIO模拟CS最稳妥
  • 寄存器状态要勤打印,尤其是FIFO计数和错误标志位

好了,SPI这块的内容就这些。记住,驱动开发没有捷径,但有了这些经验和技巧,至少能让你少走弯路。