- 前言
- SPI控制器驱动
- spi-mem framework
- 重要数据结构
- struct spi_mem
- struct spi_mem_op
- spi_controller_mem_ops
- 对外接口
- spi mem设备端
- spi-nore
- 驱动注册
- 数据读写
- spi-nand
- 驱动注册
- 数据读写
- spi controller端
- 总结
spi-mem驱动为SPI存储器生态带来一些一致性,该框架实现了在spi nor设备、spi nand设备、以及常规spi外设上复用spi控制器驱动。
Kernel版本:5.14.9
SPI控制器驱动 传统内核中,spi nor有单独的驱动,芯片厂商根据使用的spi控制器驱动,实现对应的spi-nor驱动。spi-nor驱动只分为driver和core层。但是这种情况下,该spi控制器,就不能再提供给其他外设使用了。以及随着SPI Nand的流行。spi-nor的驱动并不适用于spi-nand。
不管是单线、双线、四线模式下,对于底层控制器来说,spi-nor、spi-nand的发送逻辑是一样的,都是opcode-addr-dummy-data。且底层控制器不关心具体上层逻辑差异。所以驱动分层就显得十分重要了。
在这个背景下,spi-mem驱动应运而生。
在这个架构下,左侧是传统spi nor驱动,实现spi nor控制器驱动。上层为spi nor core层。在新的驱动框架中
- 最底层是spi controller drivers,这个控制器驱动就是传统的spi总线控制器驱动,针对spi存储设备特性,在数据结构中增加了新的回调函数集,这个后文详细介绍。
- 中间层是spi-mem驱动,提供了针对flash的指令,封装数据结构,提供发送数据、检查属性等接口。
- 最上层是flash核心层,spi-nor core、spi-nand core。核心层并不用关心底层是什么控制器,只需要调用spi-mem驱动提供的接口即可。
注:4.x版本内核后,spi-nand就已经使用spi-mem framework了。但是spi-nor直到5.x版本才使用spi-mem驱动。具体版本未作考证,所以选取了教新得到5.14.9版本内核来进行代码分析。
spi-mem frameworklinux中的spi-mem核心代码在drivers/spi/spi-mem.c。该框架提供给spi存储控制器驱动的api由include/linux/spi/spi-mem.h定义
重要数据结构 struct spi_memspi-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的名字
该结构体表示一次对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传输。
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回调。
spi-mem对上层提供flash操作接口:
int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op); bool spi_mem_supports_op(struct spi_mem *mem, const struct spi_mem_op *op); int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op);
分别对应上述spi_controller_mem_ops回调函数。
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-nore 驱动注册以通用spi nor设备端驱动程序为例,在drivers/mtd/spi-nor/core.c中:
static const struct of_device_id spi_nor_of_table[] = {
{ .compatible = "jedec,spi-nor" },
{ },
};
MODULE_DEVICE_TABLE(of, spi_nor_of_table);
static struct spi_mem_driver spi_nor_driver = {
.spidrv = {
.driver = {
.name = "spi-nor",
.of_match_table = spi_nor_of_table,
.dev_groups = spi_nor_sysfs_groups,
},
.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);
数据读写
以write为例:
spi_nor_write->spi_nor_write_data
ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
const u8 *buf)
{
if (nor->spimem)
return spi_nor_spimem_write_data(nor, to, len, buf);
return nor->controller_ops->write(nor, to, len, buf);
}
注:nor->controller_ops->write证明还是兼容原来老的spi nor驱动的。
spi_nor_write_data->spi_nor_spimem_write_data->spi_nor_spimem_exec_op
static int spi_nor_spimem_exec_op(struct spi_nor *nor, struct spi_mem_op *op)
{
int error;
error = spi_mem_adjust_op_size(nor->spimem, op);
if (error)
return error;
return spi_mem_exec_op(nor->spimem, op);
}
spi-nand
驱动注册
以通用spi nor设备端驱动程序为例,在drivers/mtd/nand/spi/core.c中:
static const struct spi_device_id spinand_ids[] = {
{ .name = "spi-nand" },
{ },
};
MODULE_DEVICE_TABLE(spi, spinand_ids);
#ifdef CONFIG_OF
static const struct of_device_id spinand_of_ids[] = {
{ .compatible = "spi-nand" },
{ },
};
MODULE_DEVICE_TABLE(of, spinand_of_ids);
#endif
static struct spi_mem_driver spinand_drv = {
.spidrv = {
.id_table = spinand_ids,
.driver = {
.name = "spi-nand",
.of_match_table = of_match_ptr(spinand_of_ids),
},
},
.probe = spinand_probe,
.remove = spinand_remove,
};
module_spi_mem_driver(spinand_drv);
数据读写
以write为例
spinand_mtd_write->spinand_write_page->spinand_program_op
static int spinand_program_op(struct spinand_device *spinand,
const struct nand_page_io_req *req)
{
struct nand_device *nand = spinand_to_nand(spinand);
unsigned int row = nanddev_pos_to_row(nand, &req->pos);
struct spi_mem_op op = SPINAND_PROG_EXEC_OP(row);
return spi_mem_exec_op(spinand->spimem, &op);
}
spi controller端
spi控制器除了需要实现spi_controller_mem_ops外,还需要根据自身是否支持dual、quad模式,设置mode_bits如下属性:
#define SPI_TX_DUAL _BITUL(8) #define SPI_TX_QUAD _BITUL(9) #define SPI_RX_DUAL _BITUL(10) #define SPI_RX_QUAD _BITUL(11) #define SPI_CS_WORD _BITUL(12) #define SPI_TX_OCTAL _BITUL(13) #define SPI_RX_OCTAL _BITUL(14)
同理,spi-device端,如果flash需要使用quad模式cmd传输,也需要在dts中,spi-device的设备节点中,设置spi-tx-bus-width=4支持quad写模式,设置spi-rx-bus-width=4支持quad读模式。
总结 总的来说spi-mem驱动设计还是非常巧妙的。上层spi-nor、spi-nand core层为flash通用层。中间spi-mem层为抽象层,只进行透传。下层不管有没有实现spi_controller_mem_ops,均可适配各种spi-controller驱动。
反过来讲,在原来的模式下,普通spi控制器如果需要外接spi-nor flash。需要再单独实现一个spi-nor控制器驱动。在spi-mem框架下,直接依赖spi-mem即可实现对spi-nor的操作,即使驱动不做任何代码修改。



