Linux SPI驱动框架(4)——spi-mem驱动

Linux SPI驱动框架(4)——spi-mem驱动,第1张

Linux SPI驱动框架(4)——spi-mem驱动

Linux SPI驱动框架
  • 前言
  • 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 framework

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

重要数据结构 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的名字
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传输。

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的 *** 作,即使驱动不做任何代码修改。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/3992778.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-10-21
下一篇 2022-10-21

发表评论

登录后才能评论

评论列表(0条)

保存