Linux学习笔记协议篇(五):SPI FLASH控制器驱动
compatibleprobe因此,首先分析imx6ull设备树上存在的QSPI设备。
SPI flash的整体调用和运转流程时这样的:
- 内核根据设备树
compatible匹配驱动。- 调用
probe函数初始化硬件。- 注册SPI控制器后,SPI设备(如Flash)通过设备树绑定到该控制器。
- 用户空间或内核其他模块可通过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@21e0000qspi:节点标签(label),可供其他节点引用。spi@21e0000:节点名称,表示这是一个SPI控制器,硬件寄存器基地址为0x21e0000。
(2) 兼容性(Compatible)
compatible = "fsl,imx6ul-qspi", "fsl,imx6sx-qspi"
定义驱动匹配的字符串,内核会按顺序尝试匹配驱动:- 优先匹配i.MX6UL专属驱动(
fsl,imx6ul-qspi) - 若无匹配,则尝试i.MX6SX的通用驱动(
fsl,imx6sx-qspi)。
- 优先匹配i.MX6UL专属驱动(
(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_en和qspi)。 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博客
更多推荐



所有评论(0)