- 前言
- 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的 *** 作,即使驱动不做任何代码修改。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)