Linux之 SPI 驱动框架- spi-mem 框架

一、框架变更的历程

1.1 旧框架图

1.2 新框架图

那么问题来了, 为什么要开发新的 SPI 存储器接口?

有了这个新的框架, SPI NOR 和SPI NAND 都可以基于相同的SPI控制器驱动进行支持了。m25p80 驱动将被修改成,使用spi-mem 接口,取代具有局限性的spi_flash_read() 接口。目前, 我们仍然有专用的SPI NOR控制器,但是最终是移除他们,并将他们移植为 drivers/spi/ 下 的普通SPI控制器驱动。

spi-mem framework

        linux 中spi-mem 的核心代码在 drivers/spi/spi-mem.c , 该框架提供给spi 存储控制器驱动的api 。

注:4.x版本内核后,spi-nand就已经使用spi-mem framework了。但是spi-nor直到5.x版本才使用spi-mem驱动。具体版本未作考证,所以选取了教新得到5.14.9版本内核来进行代码分析。

二、重要的数据结构

2.1 struct spi_mem
  spi-mem本质是一个spi总线从设备驱动,使用struct spi_mem来描述一个spi存储设备。

struct spi_mem {
    struct spi_device *spi;
    void *drvpriv;
    const char *name;
};

  • spi:底层的spi device,可以看出spi_mem是对spi_device的简单封装。
  • drvpriv:spi_mem_driver的私有数据
  • name:该spi-mem的名字
     

2.2 

struct spi_mem_op

  该结构体表示一次对spi存储器的操作。提供给上层存储器驱动使用。

struct spi_mem_op {
	struct {
		u8 nbytes;
		u8 buswidth;
		u8 dtr : 1;
		u16 opcode;
	} cmd;

	struct {
		u8 nbytes;
		u8 buswidth;
		u8 dtr : 1;
		u64 val;
	} addr;

	struct {
		u8 nbytes;
		u8 buswidth;
		u8 dtr : 1;
	} dummy;

	struct {
		u8 buswidth;
		u8 dtr : 1;
		enum spi_mem_data_dir dir;
		unsigned int nbytes;
		union {
			void *in;
			const void *out;
		} buf;
	} data;
};

通常spi存储器的操作,包括opcode(cmd)、addr、dummy、data。注意,buswidth代表single、dual、quad传输。

2.3 spi_controller_mem_ops

故名意思,提供给spi_controller注册使用的回调函数集。一个希望优化SPI存储器操作的spi控制器,都可以实现该回调函数集。

struct spi_controller_mem_ops {
	int (*adjust_op_size)(struct spi_mem *mem, struct spi_mem_op *op);
	bool (*supports_op)(struct spi_mem *mem,
			    const struct spi_mem_op *op);
	int (*exec_op)(struct spi_mem *mem,
		       const struct spi_mem_op *op);
	const char *(*get_name)(struct spi_mem *mem);
	int (*dirmap_create)(struct spi_mem_dirmap_desc *desc);
	void (*dirmap_destroy)(struct spi_mem_dirmap_desc *desc);
	ssize_t (*dirmap_read)(struct spi_mem_dirmap_desc *desc,
			       u64 offs, size_t len, void *buf);
	ssize_t (*dirmap_write)(struct spi_mem_dirmap_desc *desc,
				u64 offs, size_t len, const void *buf);
	int (*poll_status)(struct spi_mem *mem,
			   const struct spi_mem_op *op,
			   u16 mask, u16 match,
			   unsigned long initial_delay_us,
			   unsigned long polling_rate_us,
			   unsigned long timeout_ms);
};
  • adjust_op_size:调整存储器操作的数据传输大小,以符合对齐要求和最大FIFO大小的约束。用于校正单次spi存储器传输数据长度。如单次要求读取1024字节,但是控制器只支持单次512字节传输,那么在此回调中,就需要将spi_mem_op->data.nbytes限制到512字节。spi存储器的core层,会自动将分包后,后续数据的读取地址增加。如果回调中没实现,则使用spi-mem驱动框架中默认的校正接口。代码如下:
int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
{
	struct spi_controller *ctlr = mem->spi->controller;
	size_t len;

	if (ctlr->mem_ops && ctlr->mem_ops->adjust_op_size)
		return ctlr->mem_ops->adjust_op_size(mem, op);

	if (!ctlr->mem_ops || !ctlr->mem_ops->exec_op) {
		len = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;

		if (len > spi_max_transfer_size(mem->spi))
			return -EINVAL;

		op->data.nbytes = min3((size_t)op->data.nbytes,
				       spi_max_transfer_size(mem->spi),
				       spi_max_message_size(mem->spi) -
				       len);
		if (!op->data.nbytes)
			return -EINVAL;
	}

	return 0;
}
  • supports_op:spi-nor、spi-nand通常支持多种模式,单线、四线、各个模式的cmd(opcode)各不相同,在驱动初始化的时候,需要通过support_op,确认控制器是否支持该命令。只有flash和控制器都能支持的传输模式,flash才能正常工作。通常情况下,不需要实现该函数,使用spi-mem默认的即可满足需求。代码如下:
bool spi_mem_supports_op(struct spi_mem *mem, const struct spi_mem_op *op)
{
	if (spi_mem_check_op(op))
		return false;

	return spi_mem_internal_supports_op(mem, op);
}

static bool spi_mem_internal_supports_op(struct spi_mem *mem,
					 const struct spi_mem_op *op)
{
	struct spi_controller *ctlr = mem->spi->controller;

	if (ctlr->mem_ops && ctlr->mem_ops->supports_op)
		return ctlr->mem_ops->supports_op(mem, op);

	return spi_mem_default_supports_op(mem, op);
}
  • exec_op:执行存储器操作,即实现如何发送、接受一次flash的操作。不实现该回调函数,spi-mem会用默认的传输模式,即使用传统spi_message、spi_transfer方式,一次spi存储器操作,如果包含cmd、addr、dummy、data,那么单次传输需要四个spi_transfer,效率十分低下。所以,不管是支持quad模式的spi控制器、还是普通spi控制器,如果有外接spi存储器的需求,且使用spi-mem驱动框架,都建议在驱动中实现spi_controller_mem_ops回调。

Linux支持的默认的exec_op 的操作函数如下:

路径:drivers/spi/spi-dw-core.c  的函数 dw_spi_exec_mem_op()->dw_spi_write_then_read()

  • get_name: 自定义 struct spi_mem->name, 这个name 通常会传递给mtd->name, 可以通过这个来兼容不同spi 存储器的mtdparts, 不过必须要注意的是如果这个name 是动态分配的内存,则应该调用devm_xxx()相关的接口, 因为没有提供free_name 的接口。
  • dirmap_create: 创建一个直接映射的描述符,用来通过访问memory来访问存储器,当控制器能做到将spi 存储器映射到cpu 的地址空间时,可以实现这个。此接口由spi_mem_dirmap_create()函数调用。
  • dirmap_destroy:销毁dirmap_create所创建的描述符, 此接口会由spi_mem_dirmap_destroy 调用。
  • dirmap_read: 直接从memory 读取spi 存储器的数据, 由 spi_mem_dirmap_read调用。
  • dirmap_write: 直接往memory 写数据来写spi存储器的内容, 由spi_mem_dirmap_write调用。

注意:当spi_controller_mem_ops 没有实现时, core 层将通过创建多个SPI 传输组成的SPI消息,来添加对该特性的通用支持,就像以前通用SPI NOR 控制器驱动程序所做的那样。

        对于支持直接读写内存来读写flash 的控制器来说,需要对 struct spi_mem_dirmap_desc 这个的结果体进行操作:

  • mem: 该描述符所属的spi_mem 设备
  • info: 在创建描述符所需要的信息,下面会说明。
  • nordirmap: 如果spi controller 没有实现mem_ops->dirmap_create 回调函数,则设置为1;或者在调用mem_ops->dirmap_create是出错(超出映射的内存区域)时 设置为1;此值为1 时,所有跟spi_mem_dirmap_read/write()相关的函数,就会使用spi_mem_exec_op 函数来操作flash;
  • priv: 指向controller 的私有数据结构

2.4  struct spi_mem_driver

在spi存储器的设备驱动中,应该声明自己为struct spi_mem_driver:

struct spi_mem_driver {
	struct spi_driver spidrv;
	int (*probe)(struct spi_mem *mem);
	int (*remove)(struct spi_mem *mem);
	void (*shutdown)(struct spi_mem *mem);
};

该结构体集成自struct spi_driver ,spi存储器的设备驱动需要实现proberemove函数,他们传入的参数是一个spi_mem对象。

三、举 spi-nor core 的注册为例

源码路径: drivers/mtd/spi-nor/core.c

这是linux 中 通用的spi-nor 的驱动程序,我们简单看一下它是如何使用spi-mem 框架对spi 存储器进行操作的。

以spi_nor_write 为例

spi_nor_write

                -》spi_nor_write_data // 此函数返回实际写入的字节数,如果少于上一步请求写入的字节数,就会循环执行这步,直到所有请求的字节数写完。

                        -》spi_nor_spimem_write_data

                                ->if(nor->dirmap.wdesc) 执行spi_mem_dirmap_write 函数// 在spi_nor_driver 驱动中的spi_nor_probe函数中, 是有创建wdesc和rdesc的, 不过在创建过程中会根据实际情况给desc->nodirmap赋值

                                        -》如果desc->nodirmap 为真,则执行spi_mem_no_dirmap_write 函数

                                        -》否则就调用spi_controller->mem_ops->dirmap_write 回调函数来写flash

                                ->else 执行 spi_nor_spimem_exec_op 函数 // 如果其他的spi mem设备端驱动里没有创建描述符,则直接执行spi_nor_spimem_exec_op

当没有创建描述符时:

spi_nor_spimem_exec_op()-》spi_mem_exec_op()

可以看出,当spi_controller 中mem_ops 有实现时,就会调用mem_ops->exec_op 回调;否则就会通过创建由多个spi_transfer 组成的spi_message,调用spi_sync函数来支持通用spi 的传输。

四、spi mem控制器的编写步骤

通过deepseek 写的驱动框架:

#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#include <linux/dma-mapping.h>

/*
 * 定义 DIRMAP 相关参数
 */
#define DIRMAP_READ_BUF_SIZE    (4 * 1024)  // 读缓冲区大小
#define DIRMAP_WRITE_BUF_SIZE   (1 * 1024)  // 写缓冲区大小

/*
 * 控制器私有数据结构
 */
struct my_spi_controller {
    struct spi_controller *ctlr;
    
    /* dirmap 相关资源 */
    void __iomem *read_buf;
    dma_addr_t read_buf_dma;
    
    void __iomem *write_buf;
    dma_addr_t write_buf_dma;
    
    bool use_dma;
};

/*
 * SPI 存储器控制器操作结构体实现(包含 dirmap)
 */
static const struct spi_controller_mem_ops my_spi_mem_controller_ops = {
    .adjust_op_size = my_adjust_op_size,
    .supports_op = my_supports_op,
    .exec_op = my_exec_op,
    .get_name = my_get_name,
    .dirmap_create = my_dirmap_create,
    .dirmap_read = my_dirmap_read,
    .dirmap_write = my_dirmap_write,
};

/*
 * 创建 dirmap 映射
 */
static int my_dirmap_create(struct spi_mem_dirmap_desc *desc)
{
    struct spi_controller *ctlr = desc->mem->spi->controller;
    struct my_spi_controller *my_ctlr = spi_controller_get_devdata(ctlr);
    
    /* 检查是否支持请求的映射类型 */
    if ((desc->info.op_tmpl.data.dir == SPI_MEM_DATA_IN && !my_ctlr->read_buf) ||
        (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_OUT && !my_ctlr->write_buf))
        return -ENOTSUPP;
    
    /* 检查操作是否支持 */
    if (!my_supports_op(desc->mem, &desc->info.op_tmpl))
        return -ENOTSUPP;
    
    return 0;
}

/*
 * dirmap 读取操作
 */
static ssize_t my_dirmap_read(struct spi_mem_dirmap_desc *desc,
                             u64 offs, size_t len, void *buf)
{
    struct spi_controller *ctlr = desc->mem->spi->controller;
    struct my_spi_controller *my_ctlr = spi_controller_get_devdata(ctlr);
    struct spi_mem_op op = desc->info.op_tmpl;
    size_t chunk_len, remaining = len;
    ssize_t ret = 0;
    u8 *buf_ptr = buf;
    
    /* 设置地址 */
    op.addr.val = desc->info.offset + offs;
    
    while (remaining) {
        chunk_len = min_t(size_t, remaining, DIRMAP_READ_BUF_SIZE);
        op.data.nbytes = chunk_len;
        
        if (my_ctlr->use_dma && my_ctlr->read_buf_dma) {
            /* DMA 模式读取 */
            op.data.buf.in = my_ctlr->read_buf;
            
            if (my_exec_op(desc->mem, &op) < 0) {
                ret = -EIO;
                break;
            }
            
            memcpy(buf_ptr, my_ctlr->read_buf, chunk_len);
        } else {
            /* 直接模式读取 */
            op.data.buf.in = buf_ptr;
            
            if (my_exec_op(desc->mem, &op) < 0) {
                ret = -EIO;
                break;
            }
        }
        
        buf_ptr += chunk_len;
        remaining -= chunk_len;
        ret += chunk_len;
    }
    
    return ret;
}

/*
 * dirmap 写入操作
 */
static ssize_t my_dirmap_write(struct spi_mem_dirmap_desc *desc,
                              u64 offs, size_t len, const void *buf)
{
    struct spi_controller *ctlr = desc->mem->spi->controller;
    struct my_spi_controller *my_ctlr = spi_controller_get_devdata(ctlr);
    struct spi_mem_op op = desc->info.op_tmpl;
    size_t chunk_len, remaining = len;
    ssize_t ret = 0;
    const u8 *buf_ptr = buf;
    
    /* 设置地址 */
    op.addr.val = desc->info.offset + offs;
    
    while (remaining) {
        chunk_len = min_t(size_t, remaining, DIRMAP_WRITE_BUF_SIZE);
        op.data.nbytes = chunk_len;
        
        if (my_ctlr->use_dma && my_ctlr->write_buf_dma) {
            /* DMA 模式写入 */
            memcpy(my_ctlr->write_buf, buf_ptr, chunk_len);
            op.data.buf.out = my_ctlr->write_buf;
        } else {
            /* 直接模式写入 */
            op.data.buf.out = buf_ptr;
        }
        
        if (my_exec_op(desc->mem, &op) < 0) {
            ret = -EIO;
            break;
        }
        
        buf_ptr += chunk_len;
        remaining -= chunk_len;
        ret += chunk_len;
    }
    
    return ret;
}

/*
 * SPI 控制器探测函数(增强版,含 dirmap 初始化)
 */
static int my_spi_controller_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct spi_controller *ctlr;
    struct my_spi_controller *my_ctlr;
    int ret;
    
    /* 分配控制器结构 */
    ctlr = spi_alloc_controller(dev, sizeof(*my_ctlr));
    if (!ctlr)
        return -ENOMEM;
    
    my_ctlr = spi_controller_get_devdata(ctlr);
    my_ctlr->ctlr = ctlr;
    
    /* 初始化 dirmap 资源 */
    my_ctlr->use_dma = false;
    
    /* 分配 DMA 缓冲区用于读取 */
    my_ctlr->read_buf = dmam_alloc_coherent(dev, DIRMAP_READ_BUF_SIZE,
                                          &my_ctlr->read_buf_dma, GFP_KERNEL);
    if (!my_ctlr->read_buf)
        dev_warn(dev, "Failed to allocate DMA read buffer, falling back to PIO\n");
    
    /* 分配 DMA 缓冲区用于写入 */
    my_ctlr->write_buf = dmam_alloc_coherent(dev, DIRMAP_WRITE_BUF_SIZE,
                                           &my_ctlr->write_buf_dma, GFP_KERNEL);
    if (!my_ctlr->write_buf)
        dev_warn(dev, "Failed to allocate DMA write buffer, falling back to PIO\n");
    
    my_ctlr->use_dma = my_ctlr->read_buf && my_ctlr->write_buf;
    
    /* 初始化控制器参数 */
    ctlr->mem_ops = &my_spi_mem_controller_ops;
    ctlr->bus_num = -1; /* 动态分配总线号 */
    ctlr->num_chipselect = 1; /* 假设支持1个片选 */
    ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
    ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
    
    /* 设置传输限制 */
    ctlr->max_transfer_size = 4096; /* 最大传输大小 */
    ctlr->max_address_width = 3;    /* 支持24位地址 */
    
    /* 支持 dirmap */
    ctlr->mem_ops = &my_spi_mem_controller_ops;
    
    /* 注册SPI控制器 */
    ret = devm_spi_register_controller(dev, ctlr);
    if (ret) {
        dev_err(dev, "Failed to register SPI controller: %d\n", ret);
        spi_controller_put(ctlr);
        return ret;
    }
    
    return 0;
}

/*
 * SPI 控制器移除函数(增强版,含 dirmap 资源清理)
 */
static int my_spi_controller_remove(struct platform_device *pdev)
{
    struct spi_controller *ctlr = platform_get_drvdata(pdev);
    struct my_spi_controller *my_ctlr = spi_controller_get_devdata(ctlr);
    
    /* DMA 缓冲区由 devm 管理,无需显式释放 */
    
    return 0;
}

/* 其余函数(my_adjust_op_size, my_supports_op, my_exec_op, my_get_name)保持不变 */

/*
 * 平台驱动结构体
 */
static struct platform_driver my_spi_controller_driver = {
    .probe = my_spi_controller_probe,
    .remove = my_spi_controller_remove,
    .driver = {
        .name = "my-spi-controller",
        .owner = THIS_MODULE,
    },
};

module_platform_driver(my_spi_controller_driver);

MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Enhanced SPI Memory Controller Driver with Dirmap Support");
MODULE_LICENSE("GPL");

在用户代码中,可以这样使用 dirmap 功能:

struct spi_mem_dirmap_desc *desc;
struct spi_mem_dirmap_info info = {
    .offset = 0,            // 映射的起始地址
    .length = SZ_1M,        // 映射的长度
    .op_tmpl = SPI_MEM_OP(  // 操作模板
        SPI_MEM_OP_CMD(0x03, 1),    // READ 命令
        SPI_MEM_OP_ADDR(3, 0, 1),   // 3字节地址
        SPI_MEM_OP_NO_DUMMY,
        SPI_MEM_OP_DATA_IN(0, NULL, 1) // 数据输入
    )
};

// 创建直接映射
desc = spi_mem_dirmap_create(mem, &info);

// 使用直接映射读取数据
spi_mem_dirmap_read(desc, 0, len, buf);

// 销毁直接映射
spi_mem_dirmap_destroy(desc);

回答: spi-flash命令集是指用于操作SPI闪存的一组命令。在SPI存储器的设备驱动中,可以通过spi_mem_adjust_op_size函数来调整操作的大小,通过spi_mem_supports_op函数来检查存储器是否支持特定的操作,通过spi_mem_exec_op函数来执行操作。\[1\]SPI闪存通常用于存储程序代码和数据,可以通过SPI接口进行读取和写入。对于不同的SPI闪存芯片,其命令集可能会有所不同。如果您想了解特定芯片的命令集,您可以查阅该芯片的手册。例如,M25P80是ST公司的SPI闪存芯片,您可以在ST公司的手册中找到相关的命令集信息。\[2\]同时,您还可以参考相关的技术文章和论坛讨论,了解NOR闪存和NAND闪存之间的区别。\[2\]在SPI存储器的设备驱动中,还需要声明自己为struct spi_mem_driver,该结构体包含了一些与设备驱动相关的函数指针,如probe、remove和shutdown等。\[3\] #### 引用[.reference_title] - *1* *3* [Linux SPI驱动框架(4)——spi-mem驱动](https://blog.csdn.net/weixin_42262944/article/details/120807758)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [SPI的参考命令集](https://blog.csdn.net/design_logic/article/details/38986467)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值