Linux SPI总线和设备驱动架构之二:SPI通用接口层

转自:https://blog.csdn.net/DroidPhone/article/details/23932447

通过上一篇文章的介绍,我们知道,SPI通用接口层把具体的SPI设备的协议驱动和SPI控制器驱动联系在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准API,同时还为这些接口API定义了相应的数据结构,这些数据结构一部分是SPI设备,SPI协议驱动和SPI控制器的数据抽象,一分部是为了协助数据传输而定义的数据。另外,通用接口层还负责SPI系统与Linux设备模型相关的初始化工作。本章通过这些数据结构和API的讨论来对整个通用接口层进行深入的了解。

 SPI通用接口层代码在:kernel/drivers/spi/spi.c中、

SPI设备模型的初始化

通常地,根据linux设备模型的组织方式,各种设备会挂在合适的总线,设备驱动和设备通过总线互相进行匹配,使得设备能够找到正确的驱动程序进行控制和驱动,同时,性质相似的设备可以归为某一个类的设备,他们具有某些共同的设备属性,在设备模型上就是所谓的class,SPI设备也不例外,他们也遵循linux的设备模型的规则:

struct bus_type spi_bus_type = {
	.name		= "spi",
	.dev_attrs	= spi_dev_attrs,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
	.pm		= &spi_pm,
};

static struct class spi_master_class = {
	.name		= "spi_master",
	.owner		= THIS_MODULE,
	.dev_release	= spi_master_release,
};


static int __init spi_init(void)
{
	int	status;

	buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);

	status = bus_register(&spi_bus_type);


	status = class_register(&spi_master_class);

	return status;
}

可见,在初始化阶段,spi_init函数向系统注册了一个名为spi的总线类型,同事也为SPI控制器注册了一个名为spi_master_class的设备类。这样在sysfs中就会出现以下两个文件节点:

sys/bus/spi

sys/class/spi_master

代表spi总线的spi_bus_type结构的match字段指向了spi_match_device函数,该函数用于匹配spi总线上的设备驱动。

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
	const struct spi_device	*spi = to_spi_device(dev);
	const struct spi_driver	*sdrv = to_spi_driver(drv);

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	if (sdrv->id_table)
		return !!spi_match_id(sdrv->id_table, spi);

	return strcmp(spi->modalias, drv->name) == 0;
}

spi_master结构

spi控制器负责按照设定的物理信号格式在主控和spi设备之间交换数据,spi控制器数据是如何被传输的,而不关心数据结构的内容。SPI通用接口层用spi_master结构来表示一个spi控制器,我们看看他的主要字段的意义:

struct spi_master {
	struct device	dev; //spi控制器对应的device结构

	struct list_head list;//系统中可能有多个控制器,用该链表连接在一个全局链表变量上

	s16			bus_num;//该控制对应的SPI总线编号,从0开始,通常由板级代码设定

	u16			num_chipselect;//连接到该控制器上的片选信号的个数

	/* spi_device.mode flags understood by this controller driver */
	u16			mode_bits;//工作模式,由驱动解释该模式的意义

	u16			flags; //用于设定某些限制条件的标志位

	int			(*setup)(struct spi_device *spi);//回调函数,用于设置某个spi在该控制器上的工作参数

	int			(*transfer)(struct spi_device *spi,//回调函数,用于把包含数据信息的mesg结构
						struct spi_message *mesg); 加入控制器的消息链表中。

	void			(*cleanup)(struct spi_device *spi);//回调函数,当spi_master被释放时调用

	struct kthread_worker		kworker;//用于管理数据传输消息队列的工作队列线程

	struct kthread_work		pump_messages;//用于实现数据传输队列的工作队列
	struct list_head		queue;//该控制器的消息队列,所有等待传输的消息队列挂在该链表中
	struct spi_message		*cur_msg;//当前正在处理的消息队列


	int (*prepare_transfer_hardware)(struct spi_master *master);//回调函数,正式发起传送前会被调用,用于准备硬件资源
	int (*transfer_one_message)(struct spi_master *master,//单个消息的原子传送回调函数,队列
				    struct spi_message *mesg); 中的每个消息都会调用一次该回调来完成传输工作
	int (*unprepare_transfer_hardware)(struct spi_master *master);//清理回调函数
	/* gpio chip select */
	int			*cs_gpios;//片选信号所用到的gpio
};

spi_master结构通常由控制器驱动定义,然后通过以下通用接口层定义的API注册到系统中:

int spi_register_master(struct spi_master *master)


spi_device结构

SPI通用接口层用spi_device结构来表示一个spi设备,它的各个字段意义如下:

struct spi_device {
	struct device		dev;//代表该spi设备的device结构
	struct spi_master	*master;//指向该spi设备所使用的控制器
	u32			max_speed_hz;//设备的最大工作时钟频率
	u8			chip_select;//在控制器中的片选引脚编号索引
	u8			mode;//设备的工作模式,包括时钟格式,片选信号的有效电平

	u8			bits_per_word;//设备的每个单位数据所需的比特数
	int			irq;          //设备使用的irq编号

	char			modalias[SPI_NAME_SIZE];//该设备的名字,用于spi总线和驱动进行匹配
	int			cs_gpio;	/* chip select gpio */片选信号的gpio编号,通常不用我们自己设置,接口层会跟据上面的chip_select字段在spi_master结构中进行查找并赋值。

};

要向系统添加并注册一个SPI设备(外设,非SOC内部spi master),一般需要在设备树文件中,对SPI进行描述,在内核解析设备树文件的时候会调用spi_new_device函数将SPI设备注册进去内核。

        spi0: spi@e0300000 {
                compatible = "actions,act2603a";
                ......
                status = "okay";
        };

spi_driver结构

根据linux的设备模型,有device就必定有driver与之对应,上一节介绍的spi_device结构中内嵌了device结构字段dev,同样地,代表驱动程序的spi_driver结构也内嵌了device_driver结构:

struct spi_driver {
	const struct spi_device_id *id_table;
	int			(*probe)(struct spi_device *spi);
	int			(*remove)(struct spi_device *spi);
	void			(*shutdown)(struct spi_device *spi);
	int			(*suspend)(struct spi_device *spi, pm_message_t mesg);
	int			(*resume)(struct spi_device *spi);
	struct device_driver	driver;
};

id_table字段用于指定该驱动可以驱动的设备名称,总线的匹配函数会把id_table中指定的名字和设备树中指定的compatible字段进行比较,如果比较成功,spi_driver的probe回调函数会被调用,从而完成驱动程序的初始化工作,通用接口层提供如下API来完成spi_driver的注册。

int spi_register_driver(struct spi_driver *sdrv)
{
	sdrv->driver.bus = &spi_bus_type;
	if (sdrv->probe)
		sdrv->driver.probe = spi_drv_probe;
	if (sdrv->remove)
		sdrv->driver.remove = spi_drv_remove;
	if (sdrv->shutdown)
		sdrv->driver.shutdown = spi_drv_shutdown;
	return driver_register(&sdrv->driver);
}

需要注意的是,这里的spi_driver结构代表的是具体的SPI协议驱动程序。

spi_message和spi_transfer结构

要完成和SPI设备的数据传输工作,我们还需要另外两个数据结构:spi_message和spi_transfer。spi_message包含一个spi_transfer结构序列,一旦控制器接收了一个spi_message,其中的spi_transfer应该按顺序被发送,并且不能被其他spi_message打断,所以我们认为spi_message就是一次SPI数据交换的原子操作,下面我们看看这两个数据结构的定义:

struct spi_message {
	struct list_head	transfers;

	struct spi_device	*spi;

	unsigned		is_dma_mapped:1;

	/* completion is reported through a callback */
	void			(*complete)(void *context);
	void			*context;
	unsigned		actual_length;
	int			status;

	/* for optional use by whatever driver currently owns the
	 * spi_message ...  between calls to spi_async and then later
	 * complete(), that's the spi_master controller driver.
	 */
	struct list_head	queue;
	void			*state;
};

链表字段queue用于把该结构挂在代表控制器的spi_master结构的queue字段上,控制器上可以同时被加入多个spi_message进行排队,另一个链表字段transfer则用于连接挂在本message下的spi_transfer结构。complete回调函数则会在该message下的所有spi_transfer都被传输完成时调用,以便通知协议驱动处理接收到的数据以及准备下一批需要发送的数据,我们再来看看spi_transfer结构:

struct spi_transfer {
	/* it's ok if tx_buf == rx_buf (right?)
	 * for MicroWire, one buffer must be null
	 * buffers must work with dma_*map_single() calls, unless
	 *   spi_message.is_dma_mapped reports a pre-existing mapping
	 */
	const void	*tx_buf;
	void		*rx_buf;
	unsigned	len;

	dma_addr_t	tx_dma;
	dma_addr_t	rx_dma;

	unsigned	cs_change:1;
	unsigned	tx_nbits:3;
	unsigned	rx_nbits:3;
#define	SPI_NBITS_SINGLE	0x01 /* 1bit transfer */
#define	SPI_NBITS_DUAL		0x02 /* 2bits transfer */
#define	SPI_NBITS_QUAD		0x04 /* 4bits transfer */
	u8		bits_per_word;
	u16		delay_usecs;
	u32		speed_hz;

	struct list_head transfer_list;
};

首先,transfer_list链表字段用于把该transfer挂在spi_message结构中,tx_buf和rx_buf提供了非dma模式下的数据缓冲区地址,len则是需要传输数据的长度,tx_dma和rx_dma则给出了dma模式下的缓冲区地址。原则上来将,spi_transfer才是传输的最小单位,之所以又引进spi_message进行打包。我觉得原因是,有时候希望spi设备的多个不连续地址一次性写入,如果没有spi_message进行把这样的多个spi_transfer打包,因为通常真正的数据传送工作是在另一个内核线程中完成,不打包的后果就是造成更多的进行切换,效率降低,延迟增加,尤其是对于多个不连续地址的小规模数据传送而言就是更为明显。通用接口层为我们提供了一系列用于操作和维护spi_message和spi_transfer的API函数。

用于初始化spi_message结构:

static inline void spi_message_init(struct spi_message *m)

把一个spi_transfer加入到一个spi_message中和移除一个spi_transfer
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

static inline void  spi_transfer_del(struct spi_transfer *t)

以上两个API的组合,初始化一个spi_message并添加数个spi_transfer结构:

static inline void spi_message_init_with_transfers(struct spi_message *m,struct spi_transfer *xfers, unsigned int num_xfers)

分配一个自带数个spi_transfer结构的spi_message:

static inline struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags)

发起一个spi_message的传送操作:

异步版本:extern int spi_async(struct spi_device *spi, struct spi_message *message);

同步版本:extern int spi_sync(struct spi_device *spi, struct spi_message *message);

利用以上这些API函数,SPI设备的协议驱动程序就可以完成与某个SPI设备的数据交换工作,同时也可以看到,因为有通用接口层的隔离,控制器驱动对于协议驱动程序来说是透明的,也就是说,协议驱动程序只关心具体需要处理和交换的数据,无需关系控制器是如何传送这些数据的 ,spi_master, spi_message, spi_transfer这几个数据结构的关系可以用下图表示:

总结一下,协议驱动发送数据的流程大致是这样的:

定义一个spi_message结构

用spi_message_init函数初始化spi_message;

定义一个或数个spi_transfer结构,初始化并为数据准备缓冲区并赋值给spi_transfer相应的字段

通过spi_message_init函数把这些spi_transfer挂在spi_message结构下

通过使用同步方式调用spi_sync(),如果使用异步方式,调用spi_async();

另外,通用接口层也为一些简单的数据传输提供了独立的API来完成上述的组合过程:

int spi_write(struct spi_device *spi, const void *buf, size_t len);    ----    同步方式发送数据。
int spi_read(struct spi_device *spi, void *buf, size_t len);    ----    同步方式接收数据。
int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers);    ----   同步方式,直接传送数个spi_transfer,接收和发送。
int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);    ----    先写后读。
ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);    ----    写8位,然后读8位。
ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);    ----    写8位,然后读16位。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值