2. 内核SPI子系统架构:核心层、控制器驱动层、设备驱动层的关系与数据流
做SPI驱动调试,第一件事不是翻芯片手册,而是搞懂Linux内核的SPI子系统长什么样。我见过太多人一上来就对着寄存器猛写,结果数据死活出不来——其实就是没搞清楚数据是怎么从用户态流到硬件管脚的。
Linux SPI子系统分三层,说白了就是:核心层管规矩,控制器驱动层管硬件,设备驱动层管业务。这三层各司其职,数据流就像接力棒一样一层层传下去。
2.1 三层架构总览
先看一张整体结构图,我习惯把这三层想象成一个"数据管道":
这张图我建议你存下来。每次调SPI驱动,脑子里过一遍这个流程,定位问题会快很多。
2.2 SPI核心层(SPI Core)
核心层在drivers/spi/spi.c里,它不碰任何硬件寄存器。它的工作就三件:
- 管理总线:注册/注销SPI控制器,维护总线上的设备列表
- 消息队列调度:把上层发来的
spi_message排好队,按顺序发给控制器 - 提供API:给设备驱动和控制器驱动提供标准接口
核心层最重要的数据结构是struct spi_controller(老内核叫spi_master)。我调全志T113时,经常要打印这个结构体里的字段来排查问题。
struct spi_controller {
struct device dev;
struct list_head list; // 挂在总线上的设备链表
s16 bus_num; // 总线编号
u16 num_chipselect; // 片选数量
u32 mode_bits; // 支持的SPI模式位
u32 bits_per_word_mask;
u32 min_speed_hz;
u32 max_speed_hz;
int (*transfer_one)(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *transfer);
int (*prepare_message)(struct spi_controller *ctlr,
struct spi_message *msg);
int (*unprepare_message)(struct spi_controller *ctlr,
struct spi_message *msg);
/* ... 还有很多 */
};
关键点:核心层不关心你的SPI控制器是哪个厂家的,也不关心挂的是温度传感器还是LCD屏。它只负责搭桥。
2.3 控制器驱动层(SPI Controller Driver)
这一层才是真正跟硬件打交道的。全志T113的SPI控制器驱动在drivers/spi/spi-sunxi.c里(不同内核版本文件名可能略有差异)。
控制器驱动要做的事:
- 初始化硬件:配置时钟、引脚复用、中断、DMA通道
- 实现传输函数:
transfer_one是核心,负责把spi_transfer里的数据发出去 - 中断处理:处理传输完成、FIFO空/满等中断
- DMA传输:大数据量时用DMA减轻CPU负担
我记得第一次调T113的SPI时,发现数据总是错位。折腾了两天,最后发现是set_cs回调没实现——片选信号时序不对。嗯,这种坑踩过一次就记住了。
static int sunxi_spi_transfer_one(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *tfr)
{
struct sunxi_spi *sspi = spi_controller_get_devdata(ctlr);
unsigned int mclk_rate, div;
u32 reg;
/* 设置时钟分频 */
mclk_rate = clk_get_rate(sspi->mclk);
div = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
/* ... 配置寄存器 ... */
/* 设置传输模式 */
reg = sunxi_spi_read(sspi, SUNXI_SPI_CTL_REG);
/* ... 根据spi->mode设置CPOL/CPHA ... */
/* 启动传输 */
sunxi_spi_write(sspi, SUNXI_SPI_TXD_REG, *(u8 *)tfr->tx_buf);
/* ... */
}
注意:全志T113的SPI控制器有8级FIFO,每次传输前一定要清空FIFO。我曾经因为没清FIFO,导致上一次传输的残留数据混进了新数据里,排查了整整一个下午。
2.4 设备驱动层(SPI Device Driver)
设备驱动是离业务最近的一层。它通过核心层提供的API,跟具体的SPI设备通信。
典型的设备驱动要做这些事:
- probe:创建设备节点、分配缓冲区、初始化设备
- 构造spi_message:把要发送/接收的数据打包成
spi_transfer链表 - 发起同步/异步传输:调用
spi_sync或spi_async - 解析数据:把收到的原始数据转换成业务含义
举个例子,挂一个SPI温度传感器:
static int temp_sensor_probe(struct spi_device *spi)
{
struct temp_data *priv;
u8 tx_buf[2] = {0x01, 0x00}; // 读寄存器命令
u8 rx_buf[2] = {0};
struct spi_transfer tfr = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = 2,
.speed_hz = 1000000,
.bits_per_word = 8,
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&tfr, &msg);
spi_sync(spi, &msg); // 同步传输
/* 解析rx_buf得到温度值 */
/* ... */
}
经验之谈:调试设备驱动时,先用spidev用户态工具验证硬件通路。我在T113上都是先跑spidev_test,确认收发正常了,再写内核驱动。这样能快速区分是硬件问题还是驱动问题。
2.5 数据流全景
数据从用户态到硬件,到底经历了什么?我画了个流程图:
数据流的关键路径:
| 步骤 | 发生位置 | 关键操作 | 常见问题 |
|---|---|---|---|
| 1 | 用户态 | 通过设备文件发起读写 | 权限不足、设备节点未创建 |
| 2 | 设备驱动 | 构造spi_message,调用spi_sync | 消息构造错误、缓冲区对齐问题 |
| 3 | SPI Core | 消息排队,调用控制器transfer_one | 队列满、调度延迟 |
| 4 | 控制器驱动 | 配置寄存器,启动传输 | 时钟配置错、片选时序错 |
| 5 | 硬件 | FIFO移位,SCLK产生 | 电气问题、信号干扰 |
2.6 调试时的切入点
搞清楚了架构,调试就有方向了。我总结了几条经验:
- 数据没出来:先查控制器驱动层,用示波器看SCLK和MOSI有没有波形
- 数据错位:查设备驱动的消息构造,特别是
bits_per_word和len对不对 - 速度上不去:查控制器驱动的时钟树配置,全志T113的SPI时钟源可以选
- 中断风暴:查FIFO阈值设置,或者考虑切DMA模式
重要提醒:全志T113的SPI控制器在devicetree里配dmas属性时,一定要核对DMA通道号。我见过有人把SPI0的DMA通道配成了SPI1的,结果两个设备互相干扰,数据全乱套了。
嗯,三层架构和数据流就讲到这里。你想想看,搞懂了这些,后面调驱动的时候,遇到问题至少知道该去翻哪一层的代码,对吧?