SPI(Serial Peripheral Interface)是一种同步串行通信协议,广泛应用于嵌入式系统中连接微控制器与各种外设。在Linux内核中,SPI子系统采用分层设计,其中SPI Master驱动负责管理SPI控制器硬件,是整个通信链路的核心枢纽。
作为从事Linux驱动开发多年的工程师,我认为理解SPI Master驱动框架需要把握三个关键点:
SPI传输的基本单位是spi_transfer,它定义了单次数据传输的所有参数:
c复制struct spi_transfer {
const void *tx_buf; // 发送缓冲区指针
void *rx_buf; // 接收缓冲区指针
unsigned len; // 传输长度(字节)
u32 speed_hz; // 传输速率(Hz)
u16 delay_usecs; // 传输后延迟(微秒)
u8 bits_per_word; // 每字位数(通常8/16)
bool cs_change; // 传输后是否改变片选状态
};
实际开发中,一个完整的操作通常需要多个spi_transfer组成。例如读取传感器数据时,可能需要先发送命令字,再读取数据。这些相关的spi_transfer会被组织成一个spi_message:
c复制struct spi_message {
struct list_head transfers; // transfer链表头
struct spi_device *spi; // 目标SPI设备
void (*complete)(void *); // 完成回调函数
void *context; // 回调上下文
unsigned actual_length; // 实际传输字节数
int status; // 传输状态
};
spi_master结构体抽象了SPI控制器的硬件特性:
c复制struct spi_master {
struct device dev; // 关联的设备
u16 bus_num; // 总线编号
u16 num_chipselect; // 支持的片选数量
/* 传输方法 */
int (*transfer)(struct spi_device *, struct spi_message *);
int (*transfer_one)(struct spi_master *, struct spi_device *, struct spi_transfer *);
/* 队列管理 */
struct list_head queue; // 消息队列
struct spi_message *cur_msg; // 当前处理的消息
/* 硬件控制 */
int (*setup)(struct spi_device *);
int (*prepare_transfer_hardware)(struct spi_master *);
};
在驱动实现时,我们需要重点关注transfer或transfer_one方法的实现,这决定了数据传输的具体方式。
直接传输模式适用于简单的SPI控制器,其核心是实现master->transfer方法。以下是典型实现流程:
关键代码实现:
c复制static int spi_virtual_transfer(struct spi_device *spi,
struct spi_message *mesg)
{
/* 标记传输进行中 */
mesg->status = -EINPROGRESS;
/* 添加到队列 */
list_add_tail(&mesg->queue, &spi->master->queue);
/* 启动工作队列处理 */
schedule_work(&g_virtual_ws);
return 0;
}
static void spi_virtual_work(struct work_struct *work)
{
struct spi_message *mesg;
/* 从队列取出消息 */
mesg = list_entry(g_virtual_master->queue.next,
struct spi_message, queue);
list_del_init(&mesg->queue);
/* 模拟传输完成 */
mesg->status = 0;
if (mesg->complete)
mesg->complete(mesg->context);
}
注意事项:
- 必须正确设置message->status,-EINPROGRESS表示传输中,0表示成功
- 实际硬件驱动中,工作队列处理函数需要操作SPI控制器寄存器
- 多消息并发时需加锁保护队列
队列传输模式是更现代的实现方式,利用内核的kworker线程机制。核心函数调用链如下:
code复制__spi_sync()
→ __spi_queued_transfer()
→ __spi_pump_messages()
→ spi_transfer_one_message()
→ master->transfer_one()
关键实现点:
c复制static int spi_virtual_probe(struct platform_device *pdev)
{
/* 分配master和bitbang */
master = spi_alloc_master(&pdev->dev, sizeof(struct spi_bitbang));
bitbang = spi_master_get_devdata(master);
/* 设置bitbang操作 */
bitbang->txrx_bufs = spi_virtual_transfer;
bitbang->chipselect = spi_virtual_chipselect;
/* 启动队列传输 */
ret = spi_bitbang_start(bitbang);
}
c复制static int spi_virtual_transfer(struct spi_device *spi,
struct spi_transfer *transfer)
{
/* 初始化完成量 */
reinit_completion(&g_xfer_done);
/* 模拟硬件传输完成 */
complete(&g_xfer_done);
/* 等待传输完成 */
timeout = wait_for_completion_timeout(&g_xfer_done, 100);
if (!timeout)
return -ETIMEDOUT;
return transfer->len;
}
c复制static void spi_virtual_chipselect(struct spi_device *spi, int is_on)
{
/* 实际驱动中需要操作GPIO控制片选 */
if (is_on)
gpiod_set_value(spi->cs_gpiod, 0); // 片选有效
else
gpiod_set_value(spi->cs_gpiod, 1); // 片选无效
}
| 特性 | 直接传输模式 | 队列传输模式 |
|---|---|---|
| 实现复杂度 | 简单 | 中等 |
| 性能 | 较低 | 较高 |
| 并发支持 | 需自行处理 | 内置队列管理 |
| 适用场景 | 简单SPI控制器 | 支持DMA的控制器 |
根据我的项目经验,现代嵌入式系统推荐使用队列传输模式,它能更好地利用硬件特性,且与内核的电源管理框架集成更好。
传输超时问题:
数据错位问题:
性能优化技巧:
正确的设备树配置对SPI驱动至关重要:
dts复制spi_controller: spi@40013000 {
compatible = "vendor,spi-controller";
reg = <0x40013000 0x400>;
interrupts = <0 36 4>;
clocks = <&clk_periph 15>;
#address-cells = <1>;
#size-cells = <0>;
sensor@0 {
compatible = "acme,spi-sensor";
reg = <0>; // CS0
spi-max-frequency = <1000000>;
spi-cpol; // 模式0
spi-cpha;
};
};
对于支持DMA的SPI控制器,可以显著提升大块数据传输效率:
c复制static int spi_dma_transfer(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *xfer)
{
/* 准备DMA描述符 */
struct dma_async_tx_descriptor *tx_desc, *rx_desc;
/* 映射DMA缓冲区 */
xfer->tx_dma = dma_map_single(&master->dev, (void *)xfer->tx_buf,
xfer->len, DMA_TO_DEVICE);
xfer->rx_dma = dma_map_single(&master->dev, xfer->rx_buf,
xfer->len, DMA_FROM_DEVICE);
/* 配置DMA通道 */
tx_desc = dmaengine_prep_slave_single(master->dma_tx,
xfer->tx_dma, xfer->len,
DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
/* 启动DMA传输 */
dmaengine_submit(tx_desc);
dma_async_issue_pending(master->dma_tx);
/* 等待完成 */
wait_for_completion(&master->xfer_completion);
/* 解除DMA映射 */
dma_unmap_single(&master->dev, xfer->tx_dma, xfer->len, DMA_TO_DEVICE);
dma_unmap_single(&master->dev, xfer->rx_dma, xfer->len, DMA_FROM_DEVICE);
return xfer->len;
}
现代SPI驱动应支持运行时电源管理:
c复制static int spi_runtime_suspend(struct device *dev)
{
struct spi_master *master = dev_get_drvdata(dev);
/* 关闭SPI控制器时钟 */
clk_disable_unprepare(master->clk);
return 0;
}
static int spi_runtime_resume(struct device *dev)
{
struct spi_master *master = dev_get_drvdata(dev);
/* 重新初始化SPI控制器 */
clk_prepare_enable(master->clk);
spi_hw_init(master);
return 0;
}
/* 在probe函数中设置 */
pm_runtime_enable(&master->dev);
pm_runtime_set_autosuspend_delay(&master->dev, 2000);
pm_runtime_use_autosuspend(&master->dev);
在长期调试SPI驱动过程中,我总结出一个重要经验:始终先验证硬件连接和基本配置正确性,再深入调试驱动逻辑。使用示波器或逻辑分析仪观察实际波形,往往能快速定位问题是出在硬件层还是驱动层。