spi-mem
前言
spi-mem: 为SPI存储器生态带来一些一致性,该框架实现了 在spi nor设备和常规spi设备以及spi nand 设备上复用spi 控制器驱动程序。
Linux spi 存储控制器
Linux支持双线SPI和四线SPI模式已经有一段时间了(v3.12), SPI设备驱动程序可以为每个SPI传输指定I/O通道的数量。使用这种方式,对SPI存储的操作可以被分为多次SPI传输,每次SPI传输使用预定义数量的I/O通道进行传输。
这种方式可以正常工作,直到一些IP供应商决定让它们的SPI控制器更加智能,嵌入某种高级接口,可以在单个的步骤中执行SPI存储器的操作,而不是使用分开的多次传输操作。(事实上,大多数SPI控制器甚至比这更加智能,可以允许你直接将SPI存储映射到CPU的地址空间)。在这种情况下,我们需要赋予SPI控制器更多的控制权,这样它就可以决定具体该做什么,而不必从一组分散的SPI传输命令中,重建SPI存储器操作。
当时的决定是,将这些控制器专门用于一个任务,控制SPI NORs(当时这是唯一会用到双线和四线模式的情况),SPI NOR框架就是为此而创建的。SPI NOR框架用于连接SPI NOR控制器驱动和SPI NOR的逻辑代码(spi-nor 子系统),同时我们有常规的SPI控制器驱动,可以进行基础的SPI传输(spi 子系统)。然而,从硬件的角度看,能为SPI NOR提供特殊特性的SPI控制器,一般也拥有进行基本传输的能力,即可用于控制常规的SPI设备。不幸的是,基于当前的spi-nor 子系统和spi 子系统是分裂开来的情况,如果一个SPI控制器被spi-nor子系统的驱动支持了,它将无法被用于与spi子系统中的常规设备进行通信。
作为一个针对这个问题的部分的解决方案,在struct spi_nor结构体中嵌入一个const struct spi_nor_controller_ops *controller_ops,该结构体里定义了一些操作spi nor的函数指针,这允许spi nor控制器填充这些回调函数,以支持各种专用的spi nor控制器。同时这允许spi子系统中的常规spi控制器驱动提供一个较优的方式,来从SPI NOR存储中读取数据,这种方式被通用SPI NOR驱动m25p80所使用。然而,这个解决方案是部分的,因为它只优化了读取,并且仅限于SPI NORs。

是什么促使我们提出SPI存储器接口?
我们之前已经看到,基于SPI NOR框架,SPI NOR存储器已经得到了适当的支持。但NORs 并非SPI总线上唯一的存储设备,SPI NANDs 正在变得越来越流行。Peter Pan提出了一个遵循SPI NOR模型的,用于支持SPI NAND设备的框架: SPI控制器必须实现SPI NAND控制器接口才能控制SPI NAND。但是当我们更深入地参与到这个开发中时,我们很快意识到沿着这条路走会有多么麻烦,因为这意味着,如果SPI控制器想要同时控制两种设备,就必须同时实现SPI NOR和SPI NAND接口。当SPI NVRAM或任何其他类型的存储制造商决定采用SPI总线时,将会发生什么?再添加一个SPI控制器必须实现的接口?这听起来不是个好主意。
因此我们决定用另外的方式解决这个问题,尝试找出SPI NANDs和SPI NORs的共同点。SPI NORs和SPI NANDs 指令集不同,行为和约束也不同(主要是由于NOR和NAND本身的不同),但当与设备交互时,都遵循同样的SPI存储器操作语义,这也是高级控制器都在尝试优化的部分。
SPI 存储器层只是提供一种方式给SPI控制器驱动,用于传递高级SPI存储器操作,而不是让它们处理SPI传输细节并自行尝试优化它们。这同样简化了SPI存储器驱动,因为它们只需要按照SPI存储器规范发送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由 include/linux/spi/spi-mem.h定义。注意,可能linux内核版本不一样,以下说明可能有所出入,但大同小异,原理相同。
struct spi_mem
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的name
struct spi_mem_op
该结构体表示一次对spi存储器的操作:
struct spi_mem_op {
struct {
u8 nbytes;
u8 buswidth;
u16 opcode;
} cmd;
struct {
u8 nbytes;
u8 buswidth;
u64 val;
} addr;
struct {
u8 nbytes;
u8 buswidth;
} dummy;
struct {
u8 buswidth;
enum spi_mem_data_dir dir;
unsigned int nbytes;
union {
void *in;
const void *out;
} buf;
} data;
};
通常对spi存储器的操作包括cmd、addr、dummy、data。
spi mem 控制器端
一个希望优化SPI存储器操作的SPI控制器,可以实现spi_controller_mem_ops接口:
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);
};
- adjust_op_size:调整存储器操作的数据传输大小,以符合对齐要求和最大FIFO大小的约束
- supports_op:检查这个存储器操作是否支持
- exec_op:执行存储器操作,如果不支持则返回-ENOTSUPP
- 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控制器驱动程序(名为m25p80)所做的那样。
对于支持直接读写内存来读写flash的控制器来说,需要对struct spi_mem_dirmap_desc这样的结构体进行操作:
struct spi_mem_dirmap_desc {
struct spi_mem *mem;
struct spi_mem_dirmap_info info;
unsigned int nodirmap;
void *priv;
};
- mem:该描述符所属的spi_mem设备
- info:在创建描述符时所需要的信息,下面会说明;
- nodirmap:如果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的私有数据结构;
spi mem 设备端
在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存储器的设备驱动需要实现probe、remove函数,他们传入的参数是一个spi_mem对象。
我们以通用spi nor设备端驱动程序为例,在drivers/mtd/spi-nor/core.c中:
static const struct of_device_id spi_nor_of_table[] = {
/*
* Generic compatibility for SPI NOR that can be identified by the
* JEDEC READ ID opcode (0x9F). Use this, if possible.
*/
{ .compatible = "jedec,spi-nor" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, spi_nor_of_table);
/*
* REVISIT: many of these chips have deep power-down modes, which
* should clearly be entered on suspend() to minimize power use.
* And also when they're otherwise idle...
*/
static struct spi_mem_driver spi_nor_driver = {
.spidrv = {
.driver = {
.name = "spi-nor",
.of_match_table = spi_nor_of_table,
},
.id_table = spi_nor_dev_ids,
},
.probe = spi_nor_probe,
.remove = spi_nor_remove,
.shutdown = spi_nor_shutdown,
};
module_spi_mem_driver(spi_nor_driver);
这是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
static ssize_t spi_mem_no_dirmap_write(struct spi_mem_dirmap_desc *desc,
u64 offs, size_t len, const void *buf)
{
struct spi_mem_op op = desc->info.op_tmpl;
int ret;
op.addr.val = desc->info.offset + offs;
op.data.buf.out = buf;
op.data.nbytes = len;
ret = spi_mem_adjust_op_size(desc->mem, &op);
if (ret)
return ret;
ret = spi_mem_exec_op(desc->mem, &op);
if (ret)
return ret;
return op.data.nbytes;
}
从上面的函数调用来看,spi nor write最后会调用spi_mem_exec_op函数来执行对spi nor的写操作。此函数实现如下:
/**
* spi_mem_exec_op() - Execute a memory operation
* @mem: the SPI memory
* @op: the memory operation to execute
*
* Executes a memory operation.
*
* This function first checks that @op is supported and then tries to execute
* it.
*
* Return: 0 in case of success, a negative error code otherwise.
*/
int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
{
unsigned int tmpbufsize, xferpos = 0, totalxferlen = 0;
struct spi_controller *ctlr = mem->spi->controller;
struct spi_transfer xfers[4] = { };
struct spi_message msg;
u8 *tmpbuf;
int ret;
ret = spi_mem_check_op(op);
if (ret)
return ret;
if (!spi_mem_internal_supports_op(mem, op))
return -ENOTSUPP;
if (ctlr->mem_ops && !mem->spi->cs_gpiod) {
ret = spi_mem_access_start(mem);
if (ret)
return ret;
ret = ctlr->mem_ops->exec_op(mem, op);
spi_mem_access_end(mem);
/*
* Some controllers only optimize specific paths (typically the
* read path) and expect the core to use the regular SPI
* interface in other cases.
*/
if (!ret || ret != -ENOTSUPP)
return ret;
}
tmpbufsize = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
/*
* Allocate a buffer to transmit the CMD, ADDR cycles with kmalloc() so
* we're guaranteed that this buffer is DMA-able, as required by the
* SPI layer.
*/
tmpbuf = kzalloc(tmpbufsize, GFP_KERNEL | GFP_DMA);
if (!tmpbuf)
return -ENOMEM;
spi_message_init(&msg);
tmpbuf[0] = op->cmd.opcode;
xfers[xferpos].tx_buf = tmpbuf;
xfers[xferpos].len = op->cmd.nbytes;
xfers[xferpos].tx_nbits = op->cmd.buswidth;
spi_message_add_tail(&xfers[xferpos], &msg);
xferpos++;
totalxferlen++;
if (op->addr.nbytes) {
int i;
for (i = 0; i < op->addr.nbytes; i++)
tmpbuf[i + 1] = op->addr.val >>
(8 * (op->addr.nbytes - i - 1));
xfers[xferpos].tx_buf = tmpbuf + 1;
xfers[xferpos].len = op->addr.nbytes;
xfers[xferpos].tx_nbits = op->addr.buswidth;
spi_message_add_tail(&xfers[xferpos], &msg);
xferpos++;
totalxferlen += op->addr.nbytes;
}
if (op->dummy.nbytes) {
memset(tmpbuf + op->addr.nbytes + 1, 0xff, op->dummy.nbytes);
xfers[xferpos].tx_buf = tmpbuf + op->addr.nbytes + 1;
xfers[xferpos].len = op->dummy.nbytes;
xfers[xferpos].tx_nbits = op->dummy.buswidth;
spi_message_add_tail(&xfers[xferpos], &msg);
xferpos++;
totalxferlen += op->dummy.nbytes;
}
if (op->data.nbytes) {
if (op->data.dir == SPI_MEM_DATA_IN) {
xfers[xferpos].rx_buf = op->data.buf.in;
xfers[xferpos].rx_nbits = op->data.buswidth;
} else {
xfers[xferpos].tx_buf = op->data.buf.out;
xfers[xferpos].tx_nbits = op->data.buswidth;
}
xfers[xferpos].len = op->data.nbytes;
spi_message_add_tail(&xfers[xferpos], &msg);
xferpos++;
totalxferlen += op->data.nbytes;
}
ret = spi_sync(mem->spi, &msg);
kfree(tmpbuf);
if (ret)
return ret;
if (msg.actual_length != totalxferlen)
return -EIO;
return 0;
}
可以看到当 spi_controller中的mem_ops有实现时,就会调用mem_ops->exec_op回调;否则就会将通过创建由多个spi_transfer 组成的spi_message ,调用spi_sync函数来支持通用spi的传输。
spi mem 控制器驱动编写步骤
- 申请一个struct spi_controller的对象,初始化其必要的成员变量
- 根据控制器实现struct spi_controller_mem_ops操作集,并赋值给struct spi_controller的mem_ops成员。
- 最后调用devm_spi_register_controller函数把这个spi controller注册进内核
以下有个demo代码可供参考:
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
struct demo_data_struct {
struct device *dev;
//添加私有属性,比如控制器的寄存器地址,时钟等
};
static int demo_setup(struct spi_device *spi)
{
struct demo_data_struct *f = spi_master_get_devdata(spi->master);
//添加代码设置spi片选,速度
}
static int demo_exec_mem_op(struct spi_mem *mem, struct spi_mem_op *op)
{
struct demo_data_struct *f = spi_controller_get_devdata(mem->spi->master);
//添加代码执行一个spi_mem_op
}
static int demo_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
{
//添加代码调整存储器操作的数据传输大小,以符合对齐要求和最大FIFO大小的约束。
}
static const char *demo_get_name(struct spi_mem *mem)
{
struct demo_data_struct *f = spi_controller_get_devdata(mem->spi->master);
struct device *dev = &mem->spi->dev;
const char *name;
/*动态分配name的内存空间,此name会写入到mem->name,并最终可能传入到mtd->name*/
name = devm_kasprintf(dev, GFP_KERNEL,
"%s-%d", dev_name(f->dev),
mem->spi->chip_select);
if (!name) {
dev_err(dev, "failed to get memory for custom flash name\n");
return ERR_PTR(-ENOMEM);
}
return name;
}
static int demo_dirmap_create(struct spi_mem_dirmap_desc *desc)
{
//创建spi存储器到内存的映射,如果控制器支持将存储器的内容映射到cpu地址空间的话
}
static void demo_dirmap_destroy(struct spi_mem_dirmap_desc *desc)
{
//dirmap_create的逆操作
}
static ssize_t demo_dirmap_read(struct spi_mem_dirmap_desc *desc, u64 offs, size_t len, void *buf)
{
//直接从映射的内存中读取spi存储器的数据,返回实际读取的字节数,注意不要超过映射的区域
}
static ssize_t demo_dirmap_write(struct spi_mem_dirmap_desc *desc,u64 offs, size_t len, const void *buf)
{
//和dirmap_read类似
}
static const struct spi_controller_mem_ops demo_mem_ops = {
.exec_op = demo_exec_mem_op,
.adjust_op_size = demo_adjust_op_size,
.supports_op = demo_supports_op,
.get_name = demo_get_name,
.dirmap_create = demo_dirmap_create,
.dirmap_destroy = demo_dirmap_destroy,
.dirmap_read = demo_dirmap_read,
.dirmap_write = demo_dirmap_write,
};
static int demo_probe(struct platform_device *pdev)
{
struct spi_controller *ctlr;
struct demo_data_struct *f;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
ctlr = spi_alloc_master(&pdev->dev, sizeof(*f));
if (!master)
return -ENOMEM;
ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD;//根据控制器自行修改
ctlr->setup = demo_setup;
f = spi_controller_get_devdata(ctlr);
f->dev = dev;
platform_set_drvdata(pdev, f);
ctlr->bus_num = -1;
ctlr->num_chipselect = 1;//片选数
ctlr->mem_ops = &nxp_fspi_mem_ops;
ctlr->dev.of_node = np;
return devm_spi_register_controller(&pdev->dev, ctlr);//注册spi控制器
}
static int demo_remove(struct platform_device *pdev)
{
}
static const struct of_device_id demo_match[] = {
{.compatible = "xxxx" },
{},
};
static struct platform_driver demo_driver = {
.probe = demo_probe,
.remove = demo_remove,
.driver = {
.name = "ti-qspi",
.of_match_table = demo_match,
}
};
module_platform_driver(demo_driver);
参考链接
浙公网安备 33010602011771号