嵌入式Linux SPI子系统与驱动开发:从协议到实战
SPI是嵌入式系统最常用的外设总线之一,搞懂它的子系统架构和驱动开发流程,是从"调库"到"写驱动"的必经之路。本文从SPI协议基础讲起,拆解内核数据结构、传输API、设备树配置,最后给一个完整的SPI Flash驱动实战。
一、原理简析
SPI协议基础
SPI(Serial Peripheral Interface)是一种同步四线串行总线,广泛用于连接MCU与传感器、存储器、外设。全双工通信——每个时钟周期同时移出和移入一位数据。
四根信号线:
| 信号 | 方向 | 说明 |
|---|---|---|
| SCLK | 主→从 | 时钟线,由控制器产生 |
| MOSI | 主→从 | 主出从入数据线 |
| MISO | 从→主 | 主入从出数据线 |
| CS/nCS | 主→从 | 片选线,通常低电平有效 |
SPI没有统一的协议标准,不同厂商芯片的命令格式各异,这是与I2C的重要区别。
SPI四种模式
SPI通过CPOL(时钟极性)和CPHA(时钟相位)两个参数组合出四种工作模式:
| 模式 | CPOL | CPHA | 空闲时钟 | 采样边沿 |
|---|---|---|---|---|
| Mode 0 | 0 | 0 | 低电平 | 上升沿采样 |
| Mode 1 | 0 | 1 | 低电平 | 下降沿采样 |
| Mode 2 | 1 | 0 | 高电平 | 下降沿采样 |
| Mode 3 | 1 | 1 | 高电平 | 上升沿采样 |
实际开发中Mode 0和Mode 3最常用,选哪种取决于从设备的数据手册要求。
SPI子系统架构
Linux内核SPI子系统采用三层架构:
- SPI核心层(SPI Core):管理总线、设备和驱动的注册/匹配,提供数据传输通用接口
- SPI控制器驱动(Controller Driver):抽象硬件控制器,实现
spi_controller的回调函数 - SPI设备驱动(Protocol Driver):针对具体从设备(如Flash、传感器)的驱动,通过核心层API与设备通信
二、核心数据结构
spi_controller(原spi_master)
spi_controller是SPI控制器在内核中的抽象。5.15之前的内核叫spi_master,后因需支持Controller/Target双角色而重命名。6.8+内核中spi_master兼容层已移除,新代码统一用spi_controller。
struct spi_controller {
struct device dev;
s16 bus_num; // 总线编号
u16 num_chipselect; // 片选数量
u32 min_speed_hz; // 最低时钟频率
u32 max_speed_hz; // 最高时钟频率
// 关键回调
int (*setup)(struct spi_device *spi);
int (*transfer_one)(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *xfer);
void (*set_cs)(struct spi_device *spi, bool enable);
// 消息队列相关
struct kthread_worker *kworker;
struct kthread_work pump_messages;
struct list_head queue;
struct spi_message *cur_msg;
};
回调说明:
| 回调 | 粒度 | 说明 |
|---|---|---|
transfer_one |
单个spi_transfer | 只需处理单次传输,核心层负责消息调度 |
transfer_one_message |
整个spi_message | 需自行处理消息逻辑、CS控制 |
transfer |
整个spi_message | 旧接口,驱动自行管理队列,不推荐 |
优先实现
transfer_one,核心层会自动将其包装为spi_transfer_one_message,处理CS、延时、消息状态等逻辑。
spi_device
spi_device代表总线上的一个从设备,由设备树或spi_board_info创建:
struct spi_device {
struct device dev;
struct spi_controller *controller;
u32 max_speed_hz; // 最大时钟频率
u8 chip_select[SPI_CS_CNT_MAX]; // 片选号
u8 bits_per_word; // 每字位数,通常8
u32 mode; // SPI模式(CPOL/CPHA等)
int irq; // 中断号
struct gpio_desc *cs_gpiod[SPI_CS_CNT_MAX]; // CS GPIO描述符
};
mode字段的常用标志位:
| 标志 | 值 | 说明 |
|---|---|---|
SPI_CPHA |
0x01 | 时钟相位1 |
SPI_CPOL |
0x02 | 时钟极性1 |
SPI_CS_HIGH |
0x04 | CS高电平有效 |
SPI_LSB_FIRST |
0x08 | LSB先行 |
SPI_3WIRE |
0x10 | 三线模式 |
SPI_NO_CS |
0x40 | 无片选信号 |
spi_driver
spi_driver是设备驱动的核心,通过device_id表与spi_device匹配:
struct spi_driver {
const struct spi_device_id *id_table;
const struct of_device_id *of_match_table;
int (*probe)(struct spi_device *spi);
void (*remove)(struct spi_device *spi);
struct device_driver driver;
};
注册方式推荐使用module_spi_driver()宏,自动处理init/exit:
module_spi_driver(my_spi_driver);
spi_transfer与spi_message
SPI传输的基本单位:消息由多个传输段组成。
struct spi_transfer {
const void *tx_buf; // 发送缓冲区
void *rx_buf; // 接收缓冲区
unsigned len; // 传输字节数
u32 speed_hz; // 本次传输时钟频率(0则用设备默认)
u8 bits_per_word; // 本次传输字长(0则用设备默认)
unsigned cs_change:1; // 传输后是否改变CS状态
struct spi_delay delay; // 传输后延时(新API)
struct spi_delay word_delay; // 字间延时
};
struct spi_message {
struct list_head transfers; // spi_transfer链表
struct spi_device *spi; // 目标设备
void (*complete)(void *context); // 异步完成回调
void *context; // 回调上下文
int status; // 传输结果
};
delay_usecs字段已在5.15+内核中废弃,改用struct spi_delay delay,支持微秒、纳秒和时钟周期三种延时单位。
spi_delay结构定义:
struct spi_delay {
#define SPI_DELAY_UNIT_USECS 0 // 微秒
#define SPI_DELAY_UNIT_NSECS 1 // 纳秒
#define SPI_DELAY_UNIT_SCK 2 // 时钟周期
u16 value;
u8 unit;
};
三、SPI传输API
简易读写API
内核提供了三个便捷函数,适合简单场景:
// 只写
int spi_write(struct spi_device *spi, const void *buf, size_t len);
// 只读
int spi_read(struct spi_device *spi, void *buf, size_t len);
// 先写后读(常见于发送命令+读取数据)
int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx);
spi_write_then_read内部会构建两个spi_transfer并合并为一个spi_message,CS在整个过程中保持有效。
同步传输:spi_sync
int spi_sync(struct spi_device *spi, struct spi_message *message);
- 上下文限制:只能在进程上下文调用,因为会睡眠等待传输完成
- 返回值:0成功,负数为错误码
- 特点:调用后阻塞直到传输完成
异步传输:spi_async
int spi_async(struct spi_device *spi, struct spi_message *message);
- 上下文:可在任意上下文调用(包括中断上下文)
- 前提:必须提前设置
message->complete回调函数 - 返回值:0表示已提交队列,负数表示提交失败
- 注意:返回0不代表传输完成,完成状态通过回调通知
消息构建流程
手动构建消息的完整流程:
struct spi_message msg;
struct spi_transfer xfer[2];
// 1. 初始化消息
spi_message_init(&msg);
// 2. 初始化传输段
memset(xfer, 0, sizeof(xfer));
xfer[0].tx_buf = cmd_buf;
xfer[0].len = cmd_len;
xfer[1].rx_buf = data_buf;
xfer[1].len = data_len;
// 3. 将传输段添加到消息
spi_message_add_tail(&xfer[0], &msg);
spi_message_add_tail(&xfer[1], &msg);
// 4. 提交传输
int ret = spi_sync(spi, &msg);
四、设备树配置
SPI设备通过设备树声明,挂载在SPI控制器节点下:
&spi0 {
status = "okay";
/* 使用GPIO作为片选 */
cs-gpios = <&gpio0 17 GPIO_ACTIVE_LOW>,
<&gpio0 18 GPIO_ACTIVE_LOW>;
/* SPI Flash设备 */
spiflash: mx25l25635f@0 {
compatible = "jedec,spi-nor";
reg = <0>; /* 片选号0 */
spi-max-frequency = <50000000>; /* 最大50MHz */
spi-cpol; /* CPOL=1 */
spi-cpha; /* CPHA=1 → Mode 3 */
spi-cs-high; /* CS高电平有效(可选) */
};
/* SPI传感器设备 */
sensor@1 {
compatible = "vendor,sensor-xyz";
reg = <1>; /* 片选号1 */
spi-max-frequency = <10000000>; /* 最大10MHz */
/* 默认Mode 0,无需额外属性 */
};
};
设备树属性速查:
| 属性 | 说明 |
|---|---|
reg |
片选编号,从0开始 |
spi-max-frequency |
设备支持的最大时钟频率(Hz) |
spi-cpol |
设置CPOL=1 |
spi-cpha |
设置CPHA=1 |
spi-cs-high |
CS高电平有效 |
spi-3wire |
三线模式(MOSI/MISO合并) |
spi-lsb-first |
LSB先行 |
cs-gpios |
控制器节点的GPIO片选列表 |
当硬件片选不够用或需要更灵活的CS控制时,通过
cs-gpios属性指定GPIO作为片选。内核会自动将GPIO映射到对应的chip_select编号。
五、完整驱动示例:SPI Flash驱动
以下是一个SPI NOR Flash驱动的核心实现,包含RDID(读ID)、READ(读数据)、PAGE_PROGRAM(页编程)、SECTOR_ERASE(扇区擦除)四个操作:
#include <linux/module.h>
#include <linux/spi/spi.h>
#define CMD_RDID 0x9F
#define CMD_READ 0x03
#define CMD_PAGE_PROGRAM 0x02
#define CMD_SECTOR_ERASE 0x20
#define CMD_WREN 0x06
struct my_spi_flash {
struct spi_device *spi;
u8 manufacturer_id;
u8 memory_type;
u8 capacity;
};
/* 读设备ID */
static int spi_flash_read_id(struct my_spi_flash *flash)
{
int ret;
u8 cmd = CMD_RDID;
u8 id[3];
ret = spi_write_then_read(flash->spi, &cmd, 1, id, 3);
if (ret)
return ret;
flash->manufacturer_id = id[0];
flash->memory_type = id[1];
flash->capacity = id[2];
dev_info(&flash->spi->dev, "ID: %02x %02x %02x\n",
id[0], id[1], id[2]);
return 0;
}
/* 读数据 */
static int spi_flash_read(struct my_spi_flash *flash,
u32 addr, u8 *buf, size_t len)
{
struct spi_message msg;
struct spi_transfer xfer[2];
u8 cmd[4];
cmd[0] = CMD_READ;
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = addr & 0xFF;
spi_message_init(&msg);
memset(xfer, 0, sizeof(xfer));
xfer[0].tx_buf = cmd;
xfer[0].len = 4;
spi_message_add_tail(&xfer[0], &msg);
xfer[1].rx_buf = buf;
xfer[1].len = len;
spi_message_add_tail(&xfer[1], &msg);
return spi_sync(flash->spi, &msg);
}
/* 写使能 */
static int spi_flash_write_enable(struct my_spi_flash *flash)
{
u8 cmd = CMD_WREN;
return spi_write(flash->spi, &cmd, 1);
}
/* 页编程(最多256字节) */
static int spi_flash_page_program(struct my_spi_flash *flash,
u32 addr, const u8 *buf, size_t len)
{
struct spi_message msg;
struct spi_transfer xfer[2];
u8 cmd[4];
int ret;
ret = spi_flash_write_enable(flash);
if (ret)
return ret;
cmd[0] = CMD_PAGE_PROGRAM;
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = addr & 0xFF;
spi_message_init(&msg);
memset(xfer, 0, sizeof(xfer));
xfer[0].tx_buf = cmd;
xfer[0].len = 4;
spi_message_add_tail(&xfer[0], &msg);
xfer[1].tx_buf = buf;
xfer[1].len = len;
spi_message_add_tail(&xfer[1], &msg);
return spi_sync(flash->spi, &msg);
}
/* 扇区擦除(4KB) */
static int spi_flash_sector_erase(struct my_spi_flash *flash, u32 addr)
{
u8 cmd[4];
int ret;
ret = spi_flash_write_enable(flash);
if (ret)
return ret;
cmd[0] = CMD_SECTOR_ERASE;
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = addr & 0xFF;
return spi_write(flash->spi, cmd, 4);
}
static int my_spi_flash_probe(struct spi_device *spi)
{
struct my_spi_flash *flash;
flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
if (!flash)
return -ENOMEM;
flash->spi = spi;
spi_set_drvdata(spi, flash);
/* 配置SPI模式 */
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 50000000;
spi->bits_per_word = 8;
spi_setup(spi);
/* 读ID验证通信 */
return spi_flash_read_id(flash);
}
static void my_spi_flash_remove(struct spi_device *spi)
{
dev_info(&spi->dev, "removed\n");
}
static const struct of_device_id my_spi_flash_of_match[] = {
{ .compatible = "my-vendor,spi-flash" },
{ }
};
MODULE_DEVICE_TABLE(of, my_spi_flash_of_match);
static struct spi_driver my_spi_flash_driver = {
.driver = {
.name = "my-spi-flash",
.of_match_table = my_spi_flash_of_match,
},
.probe = my_spi_flash_probe,
.remove = my_spi_flash_remove,
};
module_spi_driver(my_spi_flash_driver);
MODULE_AUTHOR("Embedded Developer");
MODULE_DESCRIPTION("SPI NOR Flash Driver Example");
MODULE_LICENSE("GPL");
SPI主机控制器驱动
如果需要编写控制器驱动(通常SoC厂商已提供),核心流程如下:
static int my_spi_transfer_one(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *xfer)
{
/* 配置时钟频率和模式 */
my_spi_set_clk(spi->max_speed_hz);
my_spi_set_mode(spi->mode);
/* 执行全双工传输 */
if (xfer->tx_buf && xfer->rx_buf) {
/* 全双工 */
} else if (xfer->tx_buf) {
/* 只写 */
} else if (xfer->rx_buf) {
/* 只读,发送dummy字节 */
}
/* 返回0表示成功,负数为错误码 */
return 0;
}
static int my_spi_probe(struct platform_device *pdev)
{
struct spi_controller *ctlr;
ctlr = devm_spi_alloc_master(&pdev->dev, sizeof(struct my_spi_priv));
if (!ctlr)
return -ENOMEM;
ctlr->transfer_one = my_spi_transfer_one;
ctlr->bus_num = pdev->id;
ctlr->num_chipselect = 2;
ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
return devm_spi_register_controller(&pdev->dev, ctlr);
}
六、对比表格
SPI vs I2C
| 特性 | SPI | I2C |
|---|---|---|
| 信号线 | 4根(SCLK/MOSI/MISO/CS) | 2根(SCL/SDA) |
| 通信方式 | 全双工 | 半双工 |
| 速度 | 高(可达几十MHz) | 低(标准100K/快速400K) |
| 寻址方式 | 硬件片选(CS) | 软件地址(7/10位) |
| 协议标准化 | 无统一协议 | 有标准协议 |
| 多从设备 | 每设备需一根CS线 | 共享总线,地址区分 |
| 适用场景 | 高速数据传输 | 低速传感器/配置 |
同步vs异步传输
| 特性 | spi_sync | spi_async |
|---|---|---|
| 调用上下文 | 仅进程上下文 | 任意上下文 |
| 阻塞行为 | 阻塞等待完成 | 立即返回 |
| 完成通知 | 返回值 | complete回调 |
| 使用复杂度 | 简单 | 需管理回调 |
| 典型场景 | 常规读写操作 | 中断中传输、DMA传输 |
七、核心流程图
SPI子系统架构
SPI传输完整流程
八、常见问题解决
Q1:spi_sync调用返回-EINVAL
检查spi_setup()是否已调用,以及spi_transfer的tx_buf/rx_buf是否至少设置了一个。如果只读不写,tx_buf可以设为NULL(控制器会发送dummy字节),反之亦然。
Q2:spi_async的complete回调没有被调用
确认message->complete和message->context在提交前已设置。同时检查控制器驱动的transfer_one是否正确调用了spi_finalize_current_transfer()。
Q3:设备树配置了SPI设备但probe没有被调用
依次排查:①compatible字符串是否与驱动of_match_table一致;②控制器节点status是否为"okay";③reg指定的片选号是否在num_chipselect范围内;④用ls /sys/bus/spi/devices/确认设备是否被枚举。
Q4:SPI通信数据错位或乱码
最常见的原因是SPI模式不匹配。用示波器确认从设备要求的CPOL/CPHA,确保设备树或spi_setup()中设置的模式与器件手册一致。另外检查bits_per_word和字节序(MSB/LSB)。
Q5:CS信号行为异常
如果使用GPIO片选,确认设备树中cs-gpios属性正确,且GPIO已被控制器驱动申请。如果CS在多transfer消息中间不应改变,确保cs_change字段为0(默认值)。
Q6:新内核编译报错"spi_master未定义"
6.8+内核已移除spi_master兼容宏,统一使用spi_controller。将代码中所有spi_master替换为spi_controller,spi_alloc_master替换为spi_alloc_master(该函数名暂未改),devm_spi_register_master替换为devm_spi_register_controller。
九、总结
SPI子系统是嵌入式Linux驱动开发的必修课。本文从协议原理到内核数据结构,从传输API到设备树配置,再到完整的Flash驱动实战,走完了SPI驱动开发的整条链路。要点回顾:
- 协议层:CPOL/CPHA四种模式,Mode 0和Mode 3最常用
- 架构层:三层架构(核心层/控制器驱动/设备驱动),驱动开发者只需关注设备驱动
- 数据结构:
spi_controller/spi_device/spi_driver/spi_transfer/spi_message五个关键结构 - 传输层:
spi_sync用于进程上下文,spi_async用于中断上下文;delay_usecs已废弃,改用spi_delay - 实战层:
module_spi_driver()宏简化注册,spi_write_then_read()处理命令+数据场景