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驱动开发的几个核心模块串起来了。你看一眼,心里就有数了。
4.6 调试技巧:抓波形、看日志
调试SPI驱动,我最常用的三板斧:
- 逻辑分析仪抓波形:看SCLK是否正常,CS片选时序对不对,数据位宽是否匹配。
- 内核日志加打印:在
transfer_one里加dev_dbg,打印传输长度、模式、状态。 - 回环测试:把MOSI和MISO短接,发一串数据看能不能收回来。这是验证控制器和DMA是否正常的最快方法。
关键指标: SPI时钟频率、FIFO深度、DMA传输延迟、单次传输最大长度。这些参数直接影响吞吐量。RK3399的SPI最高支持50MHz,实测能跑到40MHz以上。
嗯,SPI驱动开发,说白了就是理解协议、配好控制器、选对传输方式。DMA不是必须的,但大数据量场景下,它是性能的关键。下一节,我们继续深入其他外设接口。