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通道等成员。
SPI控制器驱动架构 用户空间应用 SPI核心层 SPI控制器驱动 SPI控制器硬件 spidev字符设备 spi_register_master() transfer_one_message FIFO / DMA 引擎 驱动注册流程 platform_driver_register → probe → master init → spi_register_master

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打印,跟踪每一步的返回值。
SPI控制器驱动probe流程 platform_driver.probe 获取IO资源/中断/DMA ioremap寄存器基址 时钟使能 + 引脚配置 spi_alloc_master 中断/DMA初始化 spi_register_master

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。
transfer_one_message 执行流程 收到spi_message 遍历transfers链表 判断传输长度 FIFO轮询传输 DMA异步传输 spi_finalize_current_message

4.5 FIFO传输实现

FIFO(先进先出)是SPI控制器内部的数据缓冲器,用于暂存发送和接收数据。FIFO传输适用于中等长度(通常几十到几百字节)的数据块,具有低延迟、无需DMA通道的优点。

FIFO传输实现步骤:

  1. 配置FIFO阈值:设置发送FIFO空/满阈值和接收FIFO满/空阈值。
  2. 写入发送FIFO:将tx_buf中的数据逐个或批量写入发送FIFO寄存器。
  3. 等待传输完成:轮询状态寄存器,检查发送FIFO空和接收FIFO非空标志。
  4. 读取接收FIFO:从接收FIFO寄存器读取数据存入rx_buf。
  5. 处理剩余数据:当数据长度不是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导致消息挂起。
SPI DMA传输流程 申请DMA通道 dma_map_sg映射缓冲区 prep_slave_sg + submit issue_pending启动传输 完成回调 → finalize