SPI flash的整体调用和运转流程时这样的:

  1. 内核根据设备树compatible匹配驱动。
  2. 调用probe函数初始化硬件。
  3. 注册SPI控制器后,SPI设备(如Flash)通过设备树绑定到该控制器。
  4. 用户空间或内核其他模块可通过SPI接口访问设备(如MTD子系统操作Flash)。

因此,首先分析imx6ull设备树上存在的QSPI设备

一、设备树

以i.MX6UL处理器中的QSPI硬件模块为例:

			qspi: spi@21e0000 {
				#address-cells = <1>;
				#size-cells = <0>;
				compatible = "fsl,imx6ul-qspi", "fsl,imx6sx-qspi";
				reg = <0x021e0000 0x4000>, <0x60000000 0x10000000>;
				reg-names = "QuadSPI", "QuadSPI-memory";
				interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_QSPI>,
					 <&clks IMX6UL_CLK_QSPI>;
				clock-names = "qspi_en", "qspi";
				status = "disabled";
			};

/linux-5.14.10/arch/arm/boot/dts/imx6ul.dtsi

(1)节点名称与基本结构

  • qspi: spi@21e0000
    • qspi:节点标签(label),可供其他节点引用。
    • spi@21e0000:节点名称,表示这是一个SPI控制器,硬件寄存器基地址为0x21e0000

(2) 兼容性(Compatible)

  • compatible = "fsl,imx6ul-qspi", "fsl,imx6sx-qspi"
    定义驱动匹配的字符串,内核会按顺序尝试匹配驱动:
    • 优先匹配i.MX6UL专属驱动(fsl,imx6ul-qspi
    • 若无匹配,则尝试i.MX6SX的通用驱动(fsl,imx6sx-qspi)。

(3) 寄存器(Reg)

  • reg = <0x021e0000 0x4000>, <0x60000000 0x10000000>
    定义两段寄存器区域:
    • 控制寄存器:基地址0x021e0000,长度0x4000(16KB)
    • 内存映射区域:基地址0x60000000,长度0x10000000(256MB),用于直接访问QSPI连接的Flash设备。
  • reg-names = "QuadSPI", "QuadSPI-memory"
    reg中的每段区域命名,方便驱动引用。

IMX QSPI控制器支持三种模式:间接模式(标准SPI)、状态轮询模式和内存映射模式

  • 间接模式(标准SPI命令模式):通过QSPI寄存器执行全部操作(标准SPI)
  • 状态轮询模式:周期读取FLASH状态寄存器(中断产生)
  • 内存映射模式:QSPI控制器将外部Flash映射到CPU地址空间(如设备树中的0x60000000),CPU可直接通过指针访问Flash,被视为内部FLASH。

简单来说,QSPI是为了驱动SPI FLASH所设置的专用外设

(4)时钟(Clocks)
两个时钟源:

  • <&clks IMX6UL_CLK_QSPI>:QSPI控制器使能时钟。
  • <&clks IMX6UL_CLK_QSPI>:QSPI控制器功能时钟。
  • clock-names = "qspi_en", "qspi":为时钟命名,驱动通过名称获取特定时钟。

二、SPI控制器驱动

根据设备树,可以匹配的驱动:

static const struct of_device_id fsl_qspi_dt_ids[] = {
	{ .compatible = "fsl,vf610-qspi", .data = &vybrid_data, },
	{ .compatible = "fsl,imx6sx-qspi", .data = &imx6sx_data, },
	{ .compatible = "fsl,imx7d-qspi", .data = &imx7d_data, },
	{ .compatible = "fsl,imx6ul-qspi", .data = &imx6ul_data, },
	{ .compatible = "fsl,ls1021a-qspi", .data = &ls1021a_data, },
	{ .compatible = "fsl,ls2080a-qspi", .data = &ls2080a_data, },
	{ /* sentinel */ }
};

static struct platform_driver fsl_qspi_driver = {
	.driver = {
		.name	= "fsl-quadspi",
		.of_match_table = fsl_qspi_dt_ids,
		.pm =   &fsl_qspi_pm_ops,
	},
	.probe          = fsl_qspi_probe,
	.remove		= fsl_qspi_remove,
};
module_platform_driver(fsl_qspi_driver);

1、fsl_qspi_probe

/* QSPI控制器驱动的probe函数,在设备与驱动匹配时被调用 */
static int fsl_qspi_probe(struct platform_device *pdev)
{
    /* 1. 变量声明 */
    struct spi_controller *ctlr;      // SPI控制器抽象结构体
    struct device *dev = &pdev->dev;  // 当前设备指针
    struct device_node *np = dev->of_node; // 设备树节点
    struct resource *res;             // 资源描述符(寄存器/内存等)
    struct fsl_qspi *q;               // 驱动私有数据结构
    int ret;                          // 错误码

    /* 2. 分配一个SPI控制器对象,并预留私有数据空间(fsl_qspi) */
    ctlr = spi_alloc_master(&pdev->dev, sizeof(*q));
    ...

    /* 3. 声明控制器支持的SPI模式:双线(Dual)和四线(Quad)传输(收发均支持) */
    ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD |  // 支持双线/四线接收
                     SPI_TX_DUAL | SPI_TX_QUAD;   // 支持双线/四线发送

    /* 4. 初始化私有数据 */
    q = spi_controller_get_devdata(ctlr);  // 获取私有数据指针
    q->dev = dev;  // 保存设备指针
    q->devtype_data = of_device_get_match_data(dev); // 获取芯片特定数据
    ...
    /* 5. 绑定私有数据到平台设备 */
    platform_set_drvdata(pdev, q);

    /* 6. 获取并映射控制寄存器 */
    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "QuadSPI");
    q->iobase = devm_ioremap_resource(dev, res);  // 映射寄存器到虚拟地址
    ...

    /* 7. 获取并映射内存区域(用于XIP模式) */
    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "QuadSPI-memory");
    q->memmap_phy = res->start;  // 物理基地址
    /* 映射大小为AHB缓冲区大小的4倍(对应4个片选信号) */
    q->ahb_addr = devm_ioremap(dev, q->memmap_phy, 
                              (q->devtype_data->ahb_buf_size * 4));
    ...

    /* 8. 获取时钟资源 */
    q->clk_en = devm_clk_get(dev, "qspi_en");  // 使能时钟
    ...

    q->clk = devm_clk_get(dev, "qspi");  // 主时钟
    ...

    /* 9. 使能时钟 */
    ret = fsl_qspi_clk_prep_enable(q);
    ...

    /* 10. 获取中断号并注册中断处理函数 */
    ret = platform_get_irq(pdev, 0);  // 获取中断号
    ...
    ret = devm_request_irq(dev, ret, fsl_qspi_irq_handler, 
                          0, pdev->name, q);
    ...

    /* 11. 初始化互斥锁(保护并发访问) */
    mutex_init(&q->lock);

    /* 12. 配置SPI控制器参数 */
    ctlr->bus_num = -1;           // 动态分配总线号
    ctlr->num_chipselect = 4;     // 支持4个片选
    ctlr->mem_ops = &fsl_qspi_mem_ops;  // 内存映射操作函数集

    /* 13. 硬件默认初始化(设置寄存器默认值) */
    fsl_qspi_default_setup(q);

    /* 14. 关联设备树节点 */
    ctlr->dev.of_node = np;

    /* 15. 注册SPI控制器到内核 */
    ret = devm_spi_register_controller(dev, ctlr);
    ...
}

有如下几个关键点:

(1)分配SPI控制器并设置SPI模式支持

ctlr = spi_alloc_master(&pdev->dev, sizeof(*q));
ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
  • spi_alloc_master:分配一个SPI控制器对象,并预留私有数据空间(fsl_qspi
  • 声明控制器支持的SPI模式:双线(Dual)和四线(Quad)传输(收发均支持)

(2)映射寄存器资源

res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "QuadSPI");
q->iobase = devm_ioremap_resource(dev, res);
  • platform_get_resource_byname:从设备树中获取名为"QuadSPI"的寄存器资源(控制寄存器基地址)。
  • devm_ioremap_resource:将物理地址映射到内核虚拟地址空间。
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "QuadSPI-memory");
q->memmap_phy = res->start;
q->ahb_addr = devm_ioremap(dev, q->memmap_phy, (q->devtype_data->ahb_buf_size * 4));
  • 映射QSPI的内存映射区域(用于XIP模式),大小为AHB缓冲区大小的4倍(支持4个片选信号)

(3)时钟配置

q->clk_en = devm_clk_get(dev, "qspi_en");
q->clk = devm_clk_get(dev, "qspi");
ret = fsl_qspi_clk_prep_enable(q);
  • 获取设备树中定义的时钟(qspi_enqspi)。
  • fsl_qspi_clk_prep_enable:启用时钟(实际函数需查看实现)。

(4)中断设置

ret = platform_get_irq(pdev, 0);
ret = devm_request_irq(dev, ret, fsl_qspi_irq_handler, 0, pdev->name, q);
  • platform_get_irq:获取中断号。
  • devm_request_irq:注册中断处理函数fsl_qspi_irq_handler

(5)初始化互斥锁和SPI控制器参数

mutex_init(&q->lock);
ctlr->bus_num = -1;          // 动态分配总线号
ctlr->num_chipselect = 4;    // 支持4个片选
ctlr->mem_ops = &fsl_qspi_mem_ops; // 内存映射操作函数集
  • mem_ops:提供内存映射模式的支持(如XIP操作的exec_op方法)。其中:
static const struct spi_controller_mem_ops fsl_qspi_mem_ops = {
	.adjust_op_size = fsl_qspi_adjust_op_size,
	.supports_op = fsl_qspi_supports_op,
	.exec_op = fsl_qspi_exec_op,
	.get_name = fsl_qspi_get_name,
};

(6)硬件默认配置和注册SPI控制器

fsl_qspi_default_setup(q);
ret = devm_spi_register_controller(dev, ctlr);
  • 初始化QSPI控制器的默认寄存器值(如时钟分频、FIFO阈值等)。
  • 将控制器注册到SPI子系统,使SPI设备(如Flash)可以绑定到该控制器。

2、fsl_qspi_default_setup(q)

/* 初始化QSPI控制器的默认硬件配置 */
static int fsl_qspi_default_setup(struct fsl_qspi *q)
{
    void __iomem *base = q->iobase;  // 寄存器基地址
    ...
    /* 安全措施:先关闭时钟避免毛刺影响控制器 */
    fsl_qspi_clk_disable_unprep(q);

    /* 设置默认时钟频率66MHz(后续可动态调整) */
    ret = clk_set_rate(q->clk, 66000000);
    ...
    /* 重新准备并启用时钟 */
    ret = fsl_qspi_clk_prep_enable(q);
    ...

    /************************ 硬件复位流程 ************************/
    /* 触发软件复位:同时复位控制逻辑和AHB接口 */
    qspi_writel(q, QUADSPI_MCR_SWRSTSD_MASK | QUADSPI_MCR_SWRSTHD_MASK,
                base + QUADSPI_MCR);
    udelay(1);  // 等待复位完成(至少1us)

    /* 复位后禁用模块,防止意外操作 */
    qspi_writel(q, QUADSPI_MCR_MDIS_MASK | QUADSPI_MCR_RESERVED_MASK,
                base + QUADSPI_MCR);

    /********************** Flash配置清理 ***********************/
    /* 如果之前处于DDR模式,清除TDH位以切换回SDR模式 */
    if (needs_tdh_setting(q))
        qspi_writel(q, qspi_readl(q, base + QUADSPI_FLSHCR) & 
                      ~QUADSPI_FLSHCR_TDH_MASK,
                      base + QUADSPI_FLSHCR);

    /********************** 采样时序配置 ***********************/
    /* 禁用所有高级采样模式(使用基础SPI时序) */
    reg = qspi_readl(q, base + QUADSPI_SMPR);
    qspi_writel(q, reg & ~(QUADSPI_SMPR_FSDLY_MASK |  // 取消数据延迟
                          QUADSPI_SMPR_FSPHS_MASK |   // 取消相位偏移
                          QUADSPI_SMPR_HSENA_MASK |   // 禁用高速模式
                          QUADSPI_SMPR_DDRSMP_MASK),  // 禁用DDR采样
                base + QUADSPI_SMPR);

    /********************** 缓冲区配置 ************************/
    /* 禁用未使用的缓冲区(仅保留Buffer3用于AHB访问) */
    qspi_writel(q, 0, base + QUADSPI_BUF0IND);
    qspi_writel(q, 0, base + QUADSPI_BUF1IND);
    qspi_writel(q, 0, base + QUADSPI_BUF2IND);

    /* 配置Buffer3参数 */
    qspi_writel(q, QUADSPI_BFGENCR_SEQID(SEQID_LUT),  // 使用LUT序列
                q->iobase + QUADSPI_BFGENCR);
    qspi_writel(q, QUADSPI_RBCT_WMRK_MASK,  // 设置接收水位线
                base + QUADSPI_RBCT);
    qspi_writel(q, QUADSPI_BUF3CR_ALLMST_MASK |  // 允许AHB主设备访问
                QUADSPI_BUF3CR_ADATSZ(q->devtype_data->ahb_buf_size / 8), // 缓冲区大小
                base + QUADSPI_BUF3CR);

    /********************** 地址空间映射 ***********************/
    /* 处理可能的地址偏移(某些平台需要) */
    if (needs_amba_base_offset(q))
        addr_offset = q->memmap_phy;

    /* 
     * 配置四个片选对应的地址区域:
     * - 每个片选占用独立的空间(大小由ahb_buf_size决定)
     * - 地址按 size, 2*size, 3*size, 4*size 递增
     */
    qspi_writel(q, q->devtype_data->ahb_buf_size + addr_offset,
                base + QUADSPI_SFA1AD);  // 片选0
    qspi_writel(q, q->devtype_data->ahb_buf_size * 2 + addr_offset,
                base + QUADSPI_SFA2AD);  // 片选1
    qspi_writel(q, q->devtype_data->ahb_buf_size * 3 + addr_offset,
                base + QUADSPI_SFB1AD);  // 片选2
    qspi_writel(q, q->devtype_data->ahb_buf_size * 4 + addr_offset,
                base + QUADSPI_SFB2AD);  // 片选3

    q->selected = -1;  // 标记当前无片选被激活

    /********************** 模块使能 *************************/
    /* 启用QSPI控制器并锁定配置 */
    qspi_writel(q, QUADSPI_MCR_RESERVED_MASK | QUADSPI_MCR_END_CFG_MASK,
                base + QUADSPI_MCR);

    /********************** 中断配置 *************************/
    /* 清除所有 pending 中断状态 */
    qspi_writel(q, 0xffffffff, q->iobase + QUADSPI_FR);
    /* 仅使能传输完成中断(TFIE) */
    qspi_writel(q, QUADSPI_RSER_TFIE, q->iobase + QUADSPI_RSER);

    return 0;  // 初始化成功
}

关键操作:

  • [关闭时钟] --> [设置频率66MHz] --> [重新使能时钟]
  • [触发复位] --> [等待1us] --> [禁用模块]
  • 地址空间分配

3、devm_spi_register_controller

int devm_spi_register_controller(struct device *dev,
                 struct spi_controller *ctlr)
{
    int ret;

    ret = spi_register_controller(ctlr);
    if (ret)
        return ret;

    return devm_add_action_or_reset(dev, devm_spi_unregister, ctlr);
}


int spi_register_controller(struct spi_controller *ctlr)
{
    ...
    /* 操作回调检查:确保控制器实现必要方法 */
    status = spi_controller_check_ops(ctlr);
    if (status)
        return status;

    /******************** 总线号分配策略 ********************/
    /* 情况1:驱动已指定静态总线号 */
    if (ctlr->bus_num >= 0) {
        mutex_lock(&board_lock);
        id = idr_alloc(&spi_master_idr, ctlr, ctlr->bus_num,
                      ctlr->bus_num + 1, GFP_KERNEL);
        mutex_unlock(&board_lock);
        if (WARN(id < 0, "总线号分配失败"))
            return id == -ENOSPC ? -EBUSY : id;  // 总线号冲突或其他错误
        ctlr->bus_num = id;
    }
    /* 情况2:通过设备树别名分配动态总线号 */
    else if (ctlr->dev.of_node) {
        id = of_alias_get_id(ctlr->dev.of_node, "spi");
        if (id >= 0) {
            ctlr->bus_num = id;
            mutex_lock(&board_lock);
            id = idr_alloc(&spi_master_idr, ctlr, id, id + 1, GFP_KERNEL);
            mutex_unlock(&board_lock);
            if (WARN(id < 0, "别名总线号分配失败"))
                return id == -ENOSPC ? -EBUSY : id;
        }
    }
    /* 情况3:完全动态分配总线号 */
    if (ctlr->bus_num < 0) {
        first_dynamic = of_alias_get_highest_id("spi");
        first_dynamic = (first_dynamic < 0) ? 0 : first_dynamic + 1;

        mutex_lock(&board_lock);
        id = idr_alloc(&spi_master_idr, ctlr, first_dynamic, 0, GFP_KERNEL);
        mutex_unlock(&board_lock);
        if (WARN(id < 0, "动态总线号分配失败"))
            return id;
        ctlr->bus_num = id;
    }

    /******************** 数据结构初始化 ********************/
    INIT_LIST_HEAD(&ctlr->queue);           // 传输队列链表
    spin_lock_init(&ctlr->queue_lock);      // 队列自旋锁
    mutex_init(&ctlr->io_mutex);            // IO操作互斥锁
    init_completion(&ctlr->xfer_completion); // 传输完成通知机制
    ctlr->max_dma_len = ctlr->max_dma_len ?: INT_MAX;  // DMA最大长度默认值

    /* 设备注册:生成/sys/bus/spi/devices/spiX节点 */
    dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);

    /******************** 片选GPIO配置 ********************/
    if (!spi_controller_is_slave(ctlr)) {  // 仅主设备需要片选
        if (ctlr->use_gpio_descriptors) {  // 现代GPIO描述符方式
            status = spi_get_gpio_descs(ctlr);
            if (status)
                goto free_bus_id;
            ctlr->mode_bits |= SPI_CS_HIGH;  // 强制支持CS高电平
        } else {  // 传统设备树GPIO编号方式
            status = of_spi_get_gpio_numbers(ctlr);
            if (status)
                goto free_bus_id;
        }
    }

    /* 校验:必须至少有一个片选信号 */
    if (!ctlr->num_chipselect) {
        status = -EINVAL;
        goto free_bus_id;
    }

    /* 注册设备到内核设备模型 */
    status = device_add(&ctlr->dev);
    if (status < 0)
        goto free_bus_id;
    dev_dbg(dev, "已注册 %s %s\n", 
            spi_controller_is_slave(ctlr) ? "从设备" : "主控",
            dev_name(&ctlr->dev));

    /******************** 传输队列初始化 ********************/
    if (ctlr->transfer) {  // 旧式非队列驱动(已废弃)
        dev_info(dev, "警告:使用非队列控制器已过时\n");
    } else if (ctlr->transfer_one || ctlr->transfer_one_message) {  // 现代队列驱动
        status = spi_controller_initialize_queue(ctlr);
        if (status) {
            device_del(&ctlr->dev);
            goto free_bus_id;
        }
    }

    /* 初始化统计信息锁 */
    spin_lock_init(&ctlr->statistics.lock);

    /******************** 设备绑定流程 ********************/
    mutex_lock(&board_lock);
    list_add_tail(&ctlr->list, &spi_controller_list);  // 加入全局控制器列表
    /* 匹配预定义的板级设备信息(非设备树/ACPI方式) */
    list_for_each_entry(bi, &board_list, list)
        spi_match_controller_to_boardinfo(ctlr, &bi->board_info);
    mutex_unlock(&board_lock);

    /* 自动注册设备树和ACPI中定义的SPI设备 */
    of_register_spi_devices(ctlr);
    acpi_register_spi_devices(ctlr);
    return 0;  // 注册成功

    /******************** 错误处理 ********************/
free_bus_id:
    mutex_lock(&board_lock);
    idr_remove(&spi_master_idr, ctlr->bus_num);  // 释放总线号
    mutex_unlock(&board_lock);
    return status;
}

几个关键点:

(1)总线号分配,使用idr(ID Radix Tree)管理总线号,避免冲突。

(2)GPIO片选设置:支持新旧两种GPIO管理方式(描述符API vs 传统数字)。

(3)设备注册与命名:在/sys/bus/spi/devices下生成对应设备节点

dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);  // 设备名为spiX(X=总线号)
status = device_add(&ctlr->dev);  // 注册到设备模型

(4)队列初始化(消息传输)

if (ctlr->transfer) {
    dev_info(dev, "deprecated unqueued controller\n");  // 旧式非队列驱动
} else if (ctlr->transfer_one || ctlr->transfer_one_message) {
    spi_controller_initialize_queue(ctlr);  // 启动消息队列线程
}

应实现transfer_one而非transfer,以支持队列化传输

(5)设备绑定(设备树/ACPI)

of_register_spi_devices(ctlr);  // 解析设备树下的SPI设备
acpi_register_spi_devices(ctlr); // 解析ACPI表中的SPI设备

根据硬件描述信息(如设备树的spi-device节点)动态创建客户端设备。

整个spi_register_controller的逻辑时这样的:


参考文档:


【正点原子STM32】QSPI四线SPI模式(Quad-SPI存储器、间接模式、状态轮询模式、内存映射模式、命令序列、QSPI基本使用步骤、SPI FLASH基本使用步骤)-CSDN博客

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐