4. SPI控制器驱动编写
SPI控制器驱动是Linux SPI子系统的核心组成部分,负责与硬件寄存器直接交互,完成数据的收发控制。本章深入剖析SPI控制器驱动的关键实现环节,包括probe流程、master对象初始化、传输回调实现以及FIFO与DMA两种高效传输模式。
4.1 SPI控制器驱动整体架构
SPI控制器驱动遵循Linux内核的设备驱动模型,通过注册platform_driver或pci_driver来匹配设备树或ACPI中的控制器节点。驱动核心职责包括:
- 硬件初始化:配置时钟、引脚、中断、FIFO、DMA等资源。
- SPI master注册:向SPI核心层注册一个struct spi_master实例。
- 传输回调实现:实现transfer_one_message或transfer_one,完成实际的数据交换。
- 中断与DMA处理:响应硬件中断,管理DMA传输完成事件。
关键数据结构:struct spi_master是SPI控制器驱动的核心抽象,包含总线号、时钟速率、模式标志、传输回调函数指针、DMA通道等成员。
4.2 probe流程详解
probe函数是SPI控制器驱动的入口点,在设备与驱动匹配时被内核调用。一个典型的probe流程包含以下步骤:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 获取设备资源 | 通过platform_get_resource获取IO内存、中断号、DMA通道等 |
| 2 | ioremap寄存器基址 | 将物理地址映射为内核虚拟地址,用于寄存器访问 |
| 3 | 时钟与复位控制 | 使能SPI控制器时钟,释放复位信号 |
| 4 | 引脚配置(pinctrl) | 设置SPI相关GPIO为功能模式,配置片选、时钟、MISO/MOSI |
| 5 | 分配并初始化spi_master | 调用spi_alloc_master,设置总线号、模式、传输回调等 |
| 6 | DMA与中断初始化 | 申请中断,配置DMA引擎,绑定DMA通道 |
| 7 | 注册spi_master | 调用spi_register_master,将控制器注册到SPI核心 |
调试提示:probe失败常见原因包括时钟未使能、ioremap失败、中断号错误、DMA通道申请超时。建议在probe函数中增加dev_dbg打印,跟踪每一步的返回值。
4.3 master初始化关键字段
在probe函数中,驱动需要正确填充struct spi_master的各个字段,这些字段直接影响SPI总线的行为。以下是必须初始化的核心字段:
| 字段 | 类型 | 说明 | 典型值/设置 |
|---|---|---|---|
| bus_num | int | SPI总线编号,用于标识控制器 | 0, 1, 2 ... 或 -1(动态分配) |
| num_chipselect | u16 | 支持的片选数量 | 1 ~ 4 |
| mode_bits | u16 | 支持的SPI模式位掩码 | SPI_MODE_0 ~ SPI_MODE_3 |
| bits_per_word_mask | u32 | 支持的位宽掩码 | SPI_BPW_MASK(8) | SPI_BPW_MASK(16) |
| min_speed_hz | u32 | 最小SCLK频率 | 1000 (1KHz) |
| max_speed_hz | u32 | 最大SCLK频率 | 50000000 (50MHz) |
| transfer_one_message | 函数指针 | 单条消息传输回调 | 驱动实现的回调函数 |
| prepare_message | 函数指针 | 消息传输前准备 | 可选,用于配置DMA |
| unprepare_message | 函数指针 | 消息传输后清理 | 可选 |
| dma_tx/dma_rx | dma_chan* | DMA发送/接收通道 | dma_request_slave_channel |
// master初始化代码示例
struct spi_master *master;
struct spi_controller_data *cdata;
master = spi_alloc_master(&pdev->dev, sizeof(struct spi_controller_data));
if (!master)
return -ENOMEM;
master->bus_num = pdev->id; // 使用platform设备ID
master->num_chipselect = 1;
master->mode_bits = SPI_MODE_0 | SPI_MODE_1 | SPI_MODE_2 | SPI_MODE_3;
master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
master->min_speed_hz = 1000;
master->max_speed_hz = 50000000;
master->transfer_one_message = my_spi_transfer_one_message;
master->prepare_message = my_spi_prepare_message;
master->unprepare_message = my_spi_unprepare_message;
// 设置驱动私有数据
cdata = spi_master_get_devdata(master);
cdata->regs = regs_base;
cdata->irq = irq_num;
cdata->clk = clk;
// 注册master
ret = devm_spi_register_master(&pdev->dev, master);
if (ret) {
dev_err(&pdev->dev, "Failed to register SPI master: %d\n", ret);
spi_master_put(master);
return ret;
}
常见错误:未设置mode_bits导致设备无法匹配;bits_per_word_mask遗漏8位宽导致默认传输失败;max_speed_hz设置过高导致时钟分频异常。
4.4 transfer_one_message回调实现
transfer_one_message是SPI控制器驱动中最关键的传输回调。当SPI核心层需要发送一条消息(包含一个或多个spi_transfer)时,会调用此函数。驱动需要遍历消息中的所有transfer,并逐个完成数据收发。
实现要点:
- 消息队列管理:将消息加入硬件队列或直接启动传输。
- transfer遍历:使用list_for_each_entry遍历msg->transfers链表。
- 传输完成通知:传输完成后调用spi_finalize_current_message。
- 错误处理:传输超时或硬件错误时调用spi_finalize_current_message并传递错误码。
static int my_spi_transfer_one_message(struct spi_master *master,
struct spi_message *msg)
{
struct spi_transfer *transfer;
int ret = 0;
// 遍历消息中的所有transfer
list_for_each_entry(transfer, &msg->transfers, transfer_list) {
// 配置控制器参数:时钟、位宽、片选
my_spi_setup_transfer(master, msg->spi, transfer);
// 根据传输长度选择传输方式
if (transfer->len > FIFO_THRESHOLD) {
// 大块数据使用DMA
ret = my_spi_dma_transfer(master, transfer);
} else {
// 小块数据使用FIFO轮询
ret = my_spi_fifo_transfer(master, transfer);
}
if (ret) {
dev_err(&master->dev, "Transfer failed: %d\n", ret);
msg->status = ret;
spi_finalize_current_message(master);
return ret;
}
}
msg->status = 0;
spi_finalize_current_message(master);
return 0;
}
性能关键:transfer_one_message函数中应避免睡眠或长时间自旋。对于大块数据,务必使用中断或DMA异步完成,并在完成回调中调用spi_finalize_current_message。
4.5 FIFO传输实现
FIFO(先进先出)是SPI控制器内部的数据缓冲器,用于暂存发送和接收数据。FIFO传输适用于中等长度(通常几十到几百字节)的数据块,具有低延迟、无需DMA通道的优点。
FIFO传输实现步骤:
- 配置FIFO阈值:设置发送FIFO空/满阈值和接收FIFO满/空阈值。
- 写入发送FIFO:将tx_buf中的数据逐个或批量写入发送FIFO寄存器。
- 等待传输完成:轮询状态寄存器,检查发送FIFO空和接收FIFO非空标志。
- 读取接收FIFO:从接收FIFO寄存器读取数据存入rx_buf。
- 处理剩余数据:当数据长度不是FIFO深度的整数倍时,处理尾部字节。
static int my_spi_fifo_transfer(struct spi_master *master,
struct spi_transfer *transfer)
{
struct spi_controller_data *cdata = spi_master_get_devdata(master);
const u8 *tx = transfer->tx_buf;
u8 *rx = transfer->rx_buf;
int len = transfer->len;
int i;
// 清空FIFO
writel(SPI_FIFO_RESET, cdata->regs + SPI_FIFO_CTRL);
for (i = 0; i < len; i++) {
// 等待发送FIFO不满
while (readl(cdata->regs + SPI_STATUS) & SPI_TX_FULL)
cpu_relax();
// 写入发送数据
if (tx)
writeb(tx[i], cdata->regs + SPI_TX_FIFO);
else
writeb(0, cdata->regs + SPI_TX_FIFO);
// 等待接收FIFO非空
while (!(readl(cdata->regs + SPI_STATUS) & SPI_RX_NOT_EMPTY))
cpu_relax();
// 读取接收数据
if (rx)
rx[i] = readb(cdata->regs + SPI_RX_FIFO);
else
readb(cdata->regs + SPI_RX_FIFO); // 丢弃
}
return 0;
}
优化建议:对于支持批量FIFO操作的控制器,可以使用writesl/readsl等批量I/O函数,减少循环开销。同时注意FIFO深度,避免溢出或下溢。
4.6 DMA传输实现
DMA(直接内存访问)传输适用于大块数据(通常超过几百字节),能够解放CPU,实现后台数据传输。SPI控制器驱动需要与DMA引擎协同工作。
DMA传输实现要点:
| 步骤 | 操作 | DMA相关API |
|---|---|---|
| 1 | 申请DMA通道 | dma_request_slave_channel(dev, "tx") / "rx" |
| 2 | 配置DMA描述符 | dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_cyclic() |
| 3 | 提交DMA请求 | dmaengine_submit() |
| 4 | 启动DMA传输 | dma_async_issue_pending() |
| 5 | 等待完成回调 | 注册completion回调,在回调中调用spi_finalize_current_message |
| 6 | 错误处理与超时 | 使用wait_for_completion_timeout处理DMA超时 |
static int my_spi_dma_transfer(struct spi_master *master,
struct spi_transfer *transfer)
{
struct spi_controller_data *cdata = spi_master_get_devdata(master);
struct dma_async_tx_descriptor *tx_desc, *rx_desc;
struct completion *done = &cdata->dma_done;
unsigned long timeout;
int ret = 0;
// 配置DMA发送描述符
tx_desc = dmaengine_prep_slave_sg(cdata->dma_tx,
transfer->tx_sg.sgl,
transfer->tx_sg.nents,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!tx_desc)
return -ENOMEM;
// 配置DMA接收描述符
rx_desc = dmaengine_prep_slave_sg(cdata->dma_rx,
transfer->rx_sg.sgl,
transfer->rx_sg.nents,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!rx_desc) {
dmaengine_terminate_sync(cdata->dma_tx);
return -ENOMEM;
}
// 注册完成回调
reinit_completion(done);
rx_desc->callback = my_spi_dma_rx_callback;
rx_desc->callback_param = cdata;
// 提交并启动DMA
dmaengine_submit(tx_desc);
dmaengine_submit(rx_desc);
dma_async_issue_pending(cdata->dma_tx);
dma_async_issue_pending(cdata->dma_rx);
// 等待DMA完成或超时
timeout = wait_for_completion_timeout(done, SPI_DMA_TIMEOUT);
if (!timeout) {
dev_err(&master->dev, "DMA transfer timeout\n");
dmaengine_terminate_sync(cdata->dma_tx);
dmaengine_terminate_sync(cdata->dma_rx);
ret = -ETIMEDOUT;
}
return ret;
}
static void my_spi_dma_rx_callback(void *param)
{
struct spi_controller_data *cdata = param;
complete(&cdata->dma_done);
}
DMA调试陷阱:DMA传输失败常见原因包括:DMA通道未正确申请、SG列表配置错误、DMA地址未进行cache一致性处理(dma_map_sg)、DMA完成回调未正确调用spi_finalize_current_message导致消息挂起。