4、SPI驱动开发:SPI总线协议、RK SPI控制器驱动、DMA传输与实战

SPI,说白了就是同步串行通信里最常用的接口之一。很多传感器、Flash、显示屏都靠它传数据。RK平台的SPI控制器功能挺全,支持主从模式、多种时钟极性和相位,还能接DMA。这一章,我带你从协议到实战,把SPI驱动吃透。

4.1 SPI总线协议速览

SPI是四线制:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。通信由主机控制时钟,从机靠时钟边沿采样数据。

这里有个关键点——时钟极性和相位。极性(CPOL)决定空闲时时钟电平,相位(CPHA)决定数据采样边沿。组合起来就是四种模式:

模式 CPOL CPHA 空闲时钟 采样边沿
模式0 0 0 低电平 第一个跳变沿(上升沿)
模式1 0 1 低电平 第二个跳变沿(下降沿)
模式2 1 0 高电平 第一个跳变沿(下降沿)
模式3 1 1 高电平 第二个跳变沿(上升沿)
经验之谈: 我习惯先看从机数据手册,确认它支持哪种模式。模式0最通用,但有些Flash芯片只认模式3。配错了,数据全是乱的。

4.2 RK SPI控制器驱动结构

RK的SPI控制器,在Linux内核里对应 drivers/spi/spi-rockchip.c。驱动框架分三层:

  • 核心层(SPI Core):管理总线、设备和驱动匹配。
  • 控制器驱动层:实现硬件寄存器操作,包括时钟配置、FIFO控制、中断处理。
  • 设备驱动层:挂载在SPI总线上的具体设备,比如Flash、ADC。

控制器驱动主要实现 struct spi_controller 里的回调函数:

static int rockchip_spi_transfer_one(struct spi_master *master,
                                     struct spi_device *spi,
                                     struct spi_transfer *transfer)
{
    // 配置时钟分频
    // 设置传输模式(CPOL/CPHA)
    // 启动传输(PIO或DMA)
    return 0;
}

嗯,这里要注意:transfer_one 是每次传输的入口。如果传输长度超过FIFO深度(RK3399是64字节),就得考虑用DMA。

4.3 DMA传输:为什么需要它?

PIO模式,CPU要一个个字节往FIFO里塞。数据量大时,CPU占用率飙升。DMA传输,说白了就是让DMA控制器帮你搬数据,搬完再通知你。

RK平台SPI+DMA的典型配置:

static int rockchip_spi_prepare_dma(struct rockchip_spi *rs)
{
    struct dma_slave_config rxconf, txconf;

    // 配置RX通道
    rxconf.direction = DMA_DEV_TO_MEM;
    rxconf.src_addr = rs->phybase + ROCKCHIP_SPI_RXDR;
    rxconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    rxconf.src_maxburst = 4;

    // 配置TX通道
    txconf.direction = DMA_MEM_TO_DEV;
    txconf.dst_addr = rs->phybase + ROCKCHIP_SPI_TXDR;
    txconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    txconf.dst_maxburst = 4;

    dmaengine_slave_config(rs->dma_rx, &rxconf);
    dmaengine_slave_config(rs->dma_tx, &txconf);
    return 0;
}
避坑指南: 我曾经在RK3288上调试SPI+DMA,发现传输完成后回调没触发。查了半天,原来是DMA的burst size设成了8,但SPI FIFO深度只有64字节,导致溢出。后来改成4就正常了。记住:burst size不能超过FIFO深度的一半。

4.4 实战:驱动一个SPI Flash(W25Q64)

我们以W25Q64为例,写一个简单的读写驱动。设备树配置如下:

&spi1 {
    status = "okay";
    max-freq = <50000000>;
    num-cs = <1>;

    flash: w25q64@0 {
        compatible = "winbond,w25q64";
        reg = <0>;
        spi-max-frequency = <50000000>;
        spi-cpol;
        spi-cpha;
    };
};

驱动里,我们实现 probe 函数,注册MTD设备:

static int w25q64_probe(struct spi_device *spi)
{
    struct flash_info *info;
    int ret;

    // 设置SPI模式
    spi->mode = SPI_MODE_3;
    spi_setup(spi);

    // 读取JEDEC ID
    ret = w25q64_read_id(spi, &info);
    if (ret < 0) {
        dev_err(&spi->dev, "read id failed\n");
        return ret;
    }

    // 注册MTD设备
    return mtd_device_register(mtd, NULL, 0);
}

读写操作时,我建议用DMA传输大块数据(比如256字节以上),小数据用PIO。这样既省CPU,又避免DMA启动开销。

4.5 知识体系:SPI驱动开发全景图

下面这张图,把SPI驱动开发的几个核心模块串起来了。你看一眼,心里就有数了。

SPI驱动开发知识体系 SPI总线协议 RK SPI控制器驱动 DMA传输 SPI设备驱动 调试与性能优化 核心思路:协议理解 → 控制器驱动适配 → DMA加速 → 设备驱动实现 → 调试优化 每个环节都有坑,但掌握了流程,就能快速定位问题。

4.6 调试技巧:抓波形、看日志

调试SPI驱动,我最常用的三板斧:

  1. 逻辑分析仪抓波形:看SCLK是否正常,CS片选时序对不对,数据位宽是否匹配。
  2. 内核日志加打印:在 transfer_one 里加 dev_dbg,打印传输长度、模式、状态。
  3. 回环测试:把MOSI和MISO短接,发一串数据看能不能收回来。这是验证控制器和DMA是否正常的最快方法。
关键指标: SPI时钟频率、FIFO深度、DMA传输延迟、单次传输最大长度。这些参数直接影响吞吐量。RK3399的SPI最高支持50MHz,实测能跑到40MHz以上。

嗯,SPI驱动开发,说白了就是理解协议、配好控制器、选对传输方式。DMA不是必须的,但大数据量场景下,它是性能的关键。下一节,我们继续深入其他外设接口。