Linux内核SPI子系统深度实战:从SPI NOR Flash到自定义外设的完整驱动开发
SPI(Serial Peripheral Interface)是嵌入式系统中最常用的同步串行通信协议之一。从一块小小的SPI NOR Flash芯片到复杂的传感器阵列,SPI以其简单、高速、全双工的特性成为硬件设计的首选接口。然而,Linux内核中的SPI子系统远比表面看起来复杂——它涉及到Controller驱动、Device驱动、Device Tree配置、DMA传输、片选管理等多个层次的协同工作。
本文将从SPI协议的基本原理出发,深入分析Linux内核SPI子系统的架构设计,并通过完整的代码示例展示如何从零编写一个SPI主机控制器驱动和一个SPI设备驱动,最后讨论生产环境中的性能调优和故障排查。
1. SPI协议基础与Linux内核架构总览
1.1 SPI物理层信号
SPI通信需要至少四根信号线:
- SCLK:串行时钟线,由Master驱动,决定通信速率
- MOSI:主出从入,Master发送数据到Slave
- MISO:主入从出,Slave发送数据到Master
- CS:片选信号,低电平有效,用于选择特定的Slave设备
SPI的四种工作模式由CPOL(时钟极性)和CPHA(时钟相位)组合决定:
| Mode | CPOL | CPHA | 空闲时钟 | 采样边象 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 |
| 2 | 1 | 1 | 高电平 | 下降沿 |
| 3 | 1 | 0 | 高电平 | 上升沿 |
绝大多数SPI Flash器件使用Mode 0,而许多ADC/DAC芯片使用Mode 3。理解这个区别对驱动开发至关重要——时钟配置不当是最常见的通信失败原因。
1.2 Linux SPI子系统架构
Linux内核的SPI子系统由三个核心层次构成:
┌─────────────────────────────────────────┐
│ SPI Device Driver │ ← 设备驱动层(mtd/spi-nor, spi-mem等)
│ (spidev, SPI NOR, sensor, ADC/DAC...) │
├─────────────────────────────────────────┤
│ SPI Core │ ← 核心层(spi.c, spi-mem.c)
│ (注册/注销、队列管理、transfer调度) │
├─────────────────────────────────────────┤
│ SPI Controller Driver │ ← 控制器驱动层(platform_driver)
│ (BCM2835 SPI, QSPI, Designware...) │
└─────────────────────────────────────────┘
关键数据结构:
- spi_master:SPI主机控制器实例,包含操作函数集
struct spi_master - spi_device:SPI从设备实例,描述一个连接的Slave芯片
- spi_transfer:一次数据传输的时序描述(tx_buf, rx_buf, len, speed_hz, delay_usecs, bits_per_word, cs_change)
- spi_message:一个或多个
spi_transfer的原子组合,在spi_sync()调用中完成
核心代码位于drivers/spi/spi.c,其中最关键的两个API是:
/* 异步传输(使用完成量同步) */
int spi_sync(struct spi_device *spi, struct spi_message *message);
/* 异步传输(推荐高性能场景) */
int spi_async(struct spi_device *spi, struct spi_message *message);
spi_sync()内部通过spi_async()实现,附加了完成量等待机制。对于SPI Flash这类需要等待命令-响应序列的设备,spi_sync是标准做法。
2. SPI主机控制器驱动开发实战
在嵌入式SoC上,SPI控制器通常作为platform_device出现。编写一个完整的SPI Controller驱动需要实现以下几个关键部分。
2.1 驱动注册流程
#include <linux/spi/spi.h>
#include <linux/platform_device.h>
struct my_spi {
struct spi_master *master;
void __iomem *regs;
struct clk *clk;
unsigned int freq;
};
static const struct of_device_id my_spi_of_match[] = {
{ .compatible = "vendor,my-spi" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_spi_of_match);
static int my_spi_probe(struct platform_device *pdev)
{
struct spi_master *master;
struct my_spi *myspi;
int ret;
/* 1. 分配 spi_master,私有数据大小为 struct my_spi */
master = spi_alloc_master(&pdev->dev, sizeof(*myspi));
if (!master)
return -ENOMEM;
myspi = spi_master_get_devdata(master);
myspi->master = master;
/* 2. 映射寄存器 */
myspi->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(myspi->regs)) {
ret = PTR_ERR(myspi->regs);
goto err_free_master;
}
/* 3. 使能时钟 */
myspi->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(myspi->clk)) {
ret = PTR_ERR(myspi->clk);
goto err_free_master;
}
clk_prepare_enable(myspi->clk);
/* 4. 配置 spi_master 结构体 */
master->bus_num = pdev->id; /* SPI bus number */
master->num_chipselect = 4; /* 支持4个片选 */
master->mode_bits = SPI_CPOL | SPI_CPHA | /* 支持的模式 */
SPI_CS_HIGH | SPI_NO_CS;
master->setup = my_spi_setup; /* 设备初始化回调 */
master->transfer_one_message = my_spi_transfer; /* 传输函数 */
master->auto_runtime_pm = true;
master->max_speed_hz = 50000000; /* 50MHz max */
/* 5. 注册 SPI master */
ret = devm_spi_register_master(&pdev->dev, master);
if (ret)
goto err_disable_clk;
platform_set_drvdata(pdev, myspi);
return 0;
err_disable_clk:
clk_disable_unprepare(myspi->clk);
err_free_master:
spi_master_put(master);
return ret;
}
2.2 实现传输函数
传输函数是Controller驱动的核心,它负责将spi_message中的多个translate实际执行:
static int my_spi_transfer(struct spi_master *master,
struct spi_message *msg)
{
struct my_spi *myspi = spi_master_get_devdata(master);
struct spi_transfer *xfer;
unsigned int delay_usecs = 0;
int ret = 0;
spi_enable_cs(msg->spi); /* 拉低片选 */
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
unsigned int len = xfer->len;
const u8 *tx_buf = xfer->tx_buf;
u8 *rx_buf = xfer->rx_buf;
while (len--) {
u8 tx_byte = tx_buf ? *tx_buf++ : 0xFF;
/* 写入TX FIFO并等待完成 */
writel(tx_byte, myspi->regs + SPI_TXDR);
/* 等待RX FIFO非空 */
ret = readl_poll_timeout(myspi->regs + SPI_SR,
readl(myspi->regs + SPI_SR),
readl(myspi->regs + SPI_SR) & SR_RXNE,
1, 1000);
if (ret)
goto out;
/* 读取RX FIFO */
u8 rx_byte = readl(myspi->regs + SPI_RXDR);
if (rx_buf)
*rx_buf++ = rx_byte;
}
/* 处理 inter-transfer delay */
if (xfer->delay_usecs)
udelay(xfer->delay_usecs);
}
out:
if (ret)
spi_disable_cs(msg->spi);
msg->status = ret;
spi_finalize_current_message(master);
return ret;
}
2.3 DMA传输优化
对于高速SPI设备(如QSPI Flash在80MHz以上),轮询方式会大量占用CPU。生产级驱动应当集成DMA:
static int my_spi_dma_transfer(struct my_spi *myspi, struct spi_message *msg)
{
struct dma_async_tx_descriptor *tx_desc, *rx_desc;
struct spi_transfer *xfer;
dma_cookie_t tx_cookie, rx_cookie;
enum dma_ctrl_flags flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK;
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
/* 配置 TX DMA */
if (xfer->tx_buf) {
tx_desc = dmaengine_prep_slave_sg(myspi->tx_chan,
xfer->tx_sg.sgl,
xfer->tx_sg.nents,
DMA_MEM_TO_DEV,
flags);
dmaengine_submit(tx_desc);
tx_cookie = dmaengine_submit(tx_desc);
dma_async_issue_pending(myspi->tx_chan);
}
/* 配置 RX DMA */
if (xfer->rx_buf) {
rx_desc = dmaengine_prep_slave_sg(myspi->rx_chan,
xfer->rx_sg.sgl,
xfer->rx_sg.nents,
DMA_DEV_TO_MEM,
flags);
dmaengine_submit(rx_desc);
rx_cookie = dmaengine_submit(rx_desc);
dma_async_issue_pending(myspi->rx_chan);
}
/* 等待DMA完成 */
wait_for_completion(&myspi->dma_complete);
}
return 0;
}
使用DMA传输时,注意cache一致性:对于scatter-gather列表,需要调用dma_map_sg()和dma_unmap_sg()来确保CPU和DMA看到一致的数据。
3. SPI设备驱动开发实战
3.1 Device Tree配置
/* 在SoC设备树中定义SPI设备 */
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins>;
status = "okay";
flash: w25q128@0 {
compatible = "jedec,spi-nor";
reg = <0>; /* CS0 */
spi-max-frequency = <80000000>; /* 80MHz */
spi-rx-bus-width = <4>; /* Quad SPI接收 */
spi-tx-bus-width = <4>; /* Quad SPI发送 */
#address-cells = <1>;
#size-cells = <1>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot@0 {
label = "boot";
reg = <0x000000 0x100000>; /* 1MB */
};
kernel@100000 {
label = "kernel";
reg = <0x100000 0x700000>; /* 7MB */
};
};
};
sensor: bme280@1 {
compatible = "bosch,bme280";
reg = <1>; /* CS1 */
spi-max-frequency = <10000000>; /* 10MHz */
spi-cpol;
spi-cpha; /* Mode 3 */
};
};
3.2 自定义设备驱动模板
以下是一个完整的温湿度传感器驱动模板,展示了SPI设备驱动的典型结构:
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
struct my_sensor {
struct spi_device *spi;
struct iio_dev *indio_dev;
s32 temperature;
u32 humidity;
};
static int my_sensor_read_reg(struct spi_device *spi, u8 reg, u8 *val)
{
struct spi_transfer xfer[2];
struct spi_message msg;
u8 tx[2], rx[2];
/* 第一阶段:发送寄存器地址(读命令:reg | 0x80) */
tx[0] = reg | 0x80;
tx[1] = 0xFF; /* dummy byte */
spi_message_init(&msg);
memset(xfer, 0, sizeof(xfer));
xfer[0].tx_buf = tx;
xfer[0].len = 1;
spi_message_add_tail(&xfer[0], &msg);
xfer[1].tx_buf = &tx[1];
xfer[1].rx_buf = rx;
xfer[1].len = 1;
spi_message_add_tail(&xfer[1], &msg);
return spi_sync(spi, &msg);
}
static int my_sensor_read_data(struct my_sensor *sensor)
{
u8 temp_buf[3], hum_buf[2];
int ret;
/* 读取温度原始值 */
ret = my_sensor_read_reg(sensor->spi, REG_TEMP_MSB, &temp_buf[0]);
if (ret)
return ret;
ret = my_sensor_read_reg(sensor->spi, REG_TEMP_LSB, &temp_buf[1]);
if (ret)
return ret;
ret = my_sensor_read_reg(sensor->spi, REG_TEMP_XLSB, &temp_buf[2]);
if (ret)
return ret;
/* 将20-bit原始值转换为毫摄氏度 */
s32 raw_temp = (temp_buf[0] << 12) | (temp_buf[1] << 4) | (temp_buf[2] >> 4);
sensor->temperature = calculate_temp(sensor->config, raw_temp);
/* 读取湿度原始值 */
ret = my_sensor_read_reg(sensor->spi, REG_HUM_MSB, &hum_buf[0]);
if (ret)
return ret;
ret = my_sensor_read_reg(sensor->spi, REG_HUM_LSB, &hum_buf[1]);
if (ret)
return ret;
u32 raw_hum = (hum_buf[0] << 8) | hum_buf[1];
sensor->humidity = calculate_humidity(sensor->config, raw_hum);
return 0;
}
static int my_sensor_read_raw(struct iio_dev *indio_dev,
const struct iio_chan_spec *chan,
int *val, int *val2, long mask)
{
struct my_sensor *sensor = iio_priv(indio_dev);
int ret;
ret = my_sensor_read_data(sensor);
if (ret)
return ret;
switch (chan->type) {
case IIO_TEMP:
*val = sensor->temperature;
return IIO_VAL_INT;
case IIO_HUMIDITYRELATIVE:
*val = sensor->humidity;
return IIO_VAL_INT;
default:
return -EINVAL;
}
}
static const struct iio_info my_sensor_info = {
.read_raw = my_sensor_read_raw,
};
static int my_sensor_probe(struct spi_device *spi)
{
struct my_sensor *sensor;
struct iio_dev *indio_dev;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*sensor));
if (!indio_dev)
return -ENOMEM;
sensor = iio_priv(indio_dev);
sensor->spi = spi;
sensor->indio_dev = indio_dev;
indio_dev->info = &my_sensor_info;
indio_dev->name = "my-humidity-sensor";
indio_dev->channels = my_sensor_channels;
indio_dev->num_channels = ARRAY_SIZE(my_sensor_channels);
indio_dev->modes = INDIO_DIRECT_MODE;
return devm_iio_device_register(&spi->dev, indio_dev);
}
static const struct of_device_id my_sensor_of_match[] = {
{ .compatible = "vendor,my-humidity-sensor" },
{ }
};
MODULE_DEVICE_TABLE(of, my_sensor_of_match);
static const struct spi_device_id my_sensor_id[] = {
{ "my-humidity-sensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, my_sensor_id);
static struct spi_driver my_sensor_driver = {
.driver = {
.name = "my-humidity-sensor",
.of_match_table = my_sensor_of_match,
},
.probe = my_sensor_probe,
.id_table = my_sensor_id,
};
module_spi_driver(my_sensor_driver);
4. SPI-MEM子系统:新一代SPI Flash编程接口
Linux 4.19引入了spi-mem子系统,为SPI Flash提供统一的编程接口,取代了直接通过SPI通用的spi_sync()方式操作Flash。
spi-mem的核心思想是将SPI Flash的操作(读、写、擦除、状态寄存器等)抽象为标准的mem_ops接口:
static int spi_nor_exec_op(struct spi_nor *nor, const struct spi_mem_op *op)
{
struct spi_controller *ctlr = nor->spimaxctlr->master;
return ctlr->mem_ops.exec_op(nor->spimaxctlr, op);
}
一个典型的SPI Flash读操作可以简洁地表示为:
static const struct spi_mem_op spi_nor_op = {
.cmd = {
.opcode = SPINOR_OP_READ_4IO, /* Fast Read Quad I/O */
.nbytes = 1,
.buswidth = SPI_MEM_BUSWIDTH_1_LINE,
},
.addr = {
.nbytes = 4, /* 32-bit 地址模式 */
.val = addr,
.buswidth = SPI_MEM_BUSWIDTH_4_LINE,
},
.dummy = {
.nbytes = 6, /* 6 dummy cycles */
.buswidth = SPI_MEM_BUSWIDTH_4_LINE,
},
.data = {
.dir = SPI_MEM_DATA_IN,
.buf.in = buf,
.len = len,
.buswidth = SPI_MEM_BUSWIDTH_4_LINE,
},
};
使用spi-mem后,Controller驱动只需实现exec_op()回调,就能自动支持所有遵循JEDEC标准的SPI Flash。
5. 性能调优与生产经验
5.1 时序参数调优
SPI通信的时序裕度不足是硬件不稳定的常见原因:
- 时钟频率:超过芯片规格的频率会导致数据错误。建议从10MHz以下开始测试,逐步提升。
- CS建立时间:CS拉低到第一个时钟沿之间的时间必须满足芯片数据手册要求(通常1-100ns)。
- CS保持时间:最后一个时钟沿到CS拉高的时间,许多Flash芯片需要至少50ns的保持时间以满足内部编程要求。
Linux SPI框架通过spi_transfer.delay_usecs提供了inter-transfer延迟控制,但CS建立/保持时间通常需要在Controller驱动中精确配置。
5.2 信号完整性问题排查
高速SPI通信失败的排查思路:
- 降低时钟频率:如果低频下正常,高频出错→信号完整性问题
- 检查走线长度:SPI时钟频率 > 10MHz时,走线长度应 < 10cm
- 测量信号质量:用示波器检查过冲、振铃、边沿斜率
- 检查地平面:SPI信号线下必须有连续的地平面
- 终端电阻:对于长走线,串联22-33Ω电阻靠近Master端可以改善信号质量
5.3 RPM电源管理
现代嵌入式系统中,SPI Controller需要在设备空闲时进入低功耗状态。框架支持运行时电源管理:
#ifdef CONFIG_PM_SLEEP
static int __maybe_unused my_spi_suspend(struct device *dev)
{
struct my_spi *myspi = dev_get_drvdata(dev);
spi_master_suspend(myspi->master);
clk_disable_unprepare(myspi->clk);
return pinctrl_select_state(mypinctrl, mypinctrl_sleep);
}
static int __maybe_unused my_spi_resume(struct device *dev)
{
struct my_spi *my_spi = dev_get_drvdata(dev);
pinctrl_select_state(mypinctrl, mypinctrl_default);
clk_prepare_enable(my_spi->clk);
spi_master_resume(my_spi->master);
return 0;
}
#endif
6. 调试技巧
6.1 启用SPI核心调试日志
# 动态开启SPI传输追踪
echo 'module spi +p' > /sys/kernel/debug/dynamic_debug/control
echo 'module spi_mem +p' >> /sys/kernel/debug/dynamic_debug/control
# 查看SPI设备信息
cat /sys/bus/spi/devices/spi0.0/modalias
cat /sys/bus/spi/devices/spi0.0/of_node/compatible
6.2 使用spidev进行原型验证
在驱动开发完成之前,可以使用spidev字符设备进行原型验证:
# 确保spidev启用
echo spidev 0x00 > /sys/bus/spi/drivers/spidev/bind
# 测试SPI loopback
flash_tst -d /dev/spidev0.0 --dump=id
# 使用spidev_test工具
./spidev_test -D /dev/spidev0.0 -v -s 1000000
6.3 逻辑分析仪抓包
Saleae Logic或DSLogic等USB逻辑分析仪配合sigrok/PulseView软件,可以直接解码SPI时序,是驱动开发最有效的调试工具。
总结
Linux SPI子系统是一个层次分明、架构完善的子系统。掌握spi-master/controller/device三层模型和Transfer/Message机制是编写高质量SPI驱动的基础。在生产环境中:
- 优先使用spi-mem子系统操作SPI Flash
- 高频场景务必启用DMA传输
- 关注信号完整性和时序裕度
- 善用spidev和逻辑分析仪加速开发调试
SPI虽简单,但要做到高速、稳定、可靠,需要对协议、硬件、内核框架都有深入理解。希望本文能为读者提供一个全面的SPI驱动开发参考。

发表评论 取消回复