LINUX SPI设备驱动模型分析之三 SPI master 、device、driver模块分析

        本篇作为PSI驱动框架分析的第三篇文章,主要分析spi模块的master、device、driver这三个模块。在之前分析I2C模块的时候,我们知道I2C驱动模块抽象出i2c adapter、i2c driver、i2c device三个部分,分别对应于i2c控制器、i2c驱动、i2c设备。而spi模块也是抽象出了spi master、spi device、spi driver三个模块(分别对应于spi控制器、spi设备、spi驱动)。但spi驱动模块这三个子模块之间的关联与实现,与i2c驱动模块这三个子模块之间的关联也是有区别的。

spi驱动框架与i2c框架的区别

  1. i2c adapter注册到i2c总线上,通过flag区分i2c总线上的设备是i2c device还是i2c adapter,i2c adapter不会与i2c driver绑定。而spi模块的spi master并没有注册到spi总线上,其主要借助spi_device中的spi_master类型的指针完成spi device与spi master的绑定。
  2. i2c 驱动模块提供的通用字符设备,且该字符设备是与i2c adapter有所关联的(每一个i2c adapter均对应一个i2c 字符设备,通过该i2c字符设备即可访问该adapter下所有的i2c设备);而spi 通用字符设备则可以理解是为具体挂载在某一个master下的spi设备而创建的(因spi 通用字符设备的片选即确定了一个spi 设备);
  3. spi驱动模块创建了spi master class,系统中所有注册的master device均链接至该class下。

spi驱动-总线-控制器-设备模块之间的关联

这四个子模块通过借助LINUX设备-总线-驱动模型提供的数据结构以及接口函数,完成了子模块间的关联(这四个子模块关联关系的建立以及解除,即通过这几个模块间的注册与注销接口完成的)。我们下面先简要说下这几个模块间的关联,然后再分别对这几个模块进行分析说明(spi总线模块在之前已经分析过)。下面通过一张简略图描述这四者之间的关系。

  1. spi总线通过其bus_kset成员,链接至系统的总线集bus_kset上;
  2. spi中心概念的klist_devices、klist_drivers链表,完成了总线与spi设备、spi驱动之间的关联;
  3. Spi master借助device类型变量中的父子链表成员,完成与spi device的关联(父子关系关联);
  4. Spi master与spi device借助其spi模块定义的成员,完成了spi master与spi device的绑定;
  5. Spi master、spi device借助其device类型,完成了与系统设备集devices_kset的关联;
  6. Spi master借助其device类型成员变量,完成与spi_master_class类的关联(此图未画出)。

基本上通过这些关联关系,即完成了spi框架中这几个子模块间的关联操作。

spi master device的注册与注销以及相关结构体说明

Spi master用于抽象一个spi控制器,该控制器提供spi总线的通信方法等信息,同时该spi控制器的device类型的成员变量是该控制器下所有spi device的父设备。下面我们分析下该控制器的注册与注销

struct spi_master 分析

我们先分析下spi master的定义,对该结构体变量熟悉了以后,对它的的注册与注销也就基本上熟悉了。我们对主要成员进行说明:

  1. dev用于使用LINUX设备-总线-驱动模型;
  2. Bus_num、num_chipselect用于说明总线号及支持的cs个数;
  3. mode_bits表示该控制器支持的模式(即spi总线支持的四种模式);
  4. setup接口用于设置总线模式、时钟等内容;
  5. transfer说明该控制器提供的通信方法(目前在新的内核中已不推荐使用该接口,推荐使用队列方式,队列模式由spi核心提供,我们会在后面进行详细说明);
  6. kworker、kworker_task、queued、pump_messages、prepare_transfer_hardware、unprepare_transfer_hardware、unprepare_transfer_hardware这几个成员变量均是用于spi核心提供的队列通信模式。
  7. list成员用于将该spi_master链接至系统的spi_master_list链表上
struct spi_master {
	struct device	dev;
    /*用于链接系统上所有spi master*/
	struct list_head list;
	s16			bus_num;
	/*支持的cs个数*/
	u16			num_chipselect;
	u16			dma_alignment;
    /*支持的模式*/
	u16			mode_bits;
    /*单词传输的bit数*/
	u32			bits_per_word_mask;
	u16			flags;
#define SPI_MASTER_HALF_DUPLEX	BIT(0)		/* can't do full duplex */
#define SPI_MASTER_NO_RX	BIT(1)		/* can't do buffer read */
#define SPI_MASTER_NO_TX	BIT(2)		/* can't do buffer write */
	spinlock_t		bus_lock_spinlock;
	struct mutex		bus_lock_mutex;
	bool			bus_lock_flag;
	/*设置spi总线模式、时钟等*/
	int			(*setup)(struct spi_device *spi);
	/*spi 传输方法(包括读写方法,该方法中会根据)*/
	int			(*transfer)(struct spi_device *spi,
						struct spi_message *mesg);
	/* called on release() to free memory provided by spi_master */
	void			(*cleanup)(struct spi_device *spi);
	bool				queued;
    /*该控制器对应的worker kthread,当queued为true时,即创建该kthread*/
	struct kthread_worker		kworker;
    /*该kthread对应的进程描述符*/
	struct task_struct		*kworker_task;
    /*该控制器的kthread_work,用于注册到kworker上*/
	struct kthread_work		pump_messages;
	spinlock_t			queue_lock;
    /*用于链接所有需要该spi控制器要传输的spi_message类型的变量*/
	struct list_head		queue;
    /*当前要初始化的spi_message(该spi_message中的transfers链表上链接了所有本message要传输的信息)*/
	struct spi_message		*cur_msg;
    /*该spi控制器是否处于忙状态*/
	bool				busy;
	bool				running;
	bool				rt;
    /*这三个接口主要是用于spi控制器使用队列模式,其中prepare_transfer_hardware、unprepare_transfer_hardware接口
    主要用于进行数据传输前后的相关设置,而transfer_one_message接口用于传输一个spi_message类型变量中的transfers
    链表上的所有待传输的数据(spi_transfer类型的变量)*/
	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;
};

Spi master的注册

上面我们分析了spi_master的数据结构定义,现在我们分析下其注册接口spi_register_master的实现。在之前的文档中,我们已经说明spi驱动框架是按照LINUX设备-总线-驱动模型进行实现的,并没有将spi master注册到spi总线上,而仅仅将spi master注册到系统的devices_kset中,隶属于LINUX系统设备集,同时隶属于spi_master_class类(spi master device并没有注册到spi总线上,而不像i2c模块,将i2c adapter也注册到i2c总线上,从这一点上而言,spi模块还是比较好的遵守了LINUX设备-总线-驱动模型的)。

Spi master注册接口的流程图如下,主要实现的功能如下:

  1. 调用device_add,将该master对应的device成员注册到系统的设备集devices_kset上,以及链接至spi_master_class类,此处主要是使用系统的设备模型对应的接口;
  2. 针对新版内核,spi模块提供队列传输模式,为该master创建worker kthread,并与master的进行绑定(该部分在后面会进行详细说明);
  3. 将该master链接到系统的spi_master_list链表上(该变量主要是在调用spi模块的设备注册接口时,根据传递的bus_num与该链表上的master进行匹配检测,从而实现将注册的spi device与对应的master的绑定);
  4. 对系统上board_list链表上注册的spi board info(尚未创建对应的spi device),若其属于该master,则为其创建spi device并注册到spi总线上,同时完成spi device与该master的绑定操作;
  5. 对于通过设备树创建的master,针对该master的of_node节点上所有的设备子节点,均创建对应的spi device,并将其注册到spi总线上,同时完成spi device与master的绑定操作。

针对spi device,其父设备均为其所绑定的spi master。

Spi master的注销

以上为spi master注册相关的处理流程分析,而针对spi master注销的流程,其主要流程与上

面的流程刚发相反,主要内容如下:

  1. 将该master从spi_master_list链表上移除;
  2. 针对该spi master上所有的子设备,调用device_unregister将其从spi总线上移除,并完成与spi driver的解绑;
  3. 若该spi master支持队列传输模式,则关闭该master对应的worker kthread。
  4. 针对该spi master对应的device,调用device_unregister,移除该设备(此处仅仅将该设备从系统设备集devices_kset上注销以及从spi_master_class类上解除对该设备的链接)

Spi device的注册与注销

上面我们分析了spi总线的注册与注销,此处我们分析spi设备的注册与注销,首先我们分析下

spi device对应数据结构定义。主要包含该设备所依附的master、传输模式、传输速率、片选、device类型的成员变量用于实现LINUX设备模型,完成注册到spi总线上等。

struct spi_device {
	struct device		dev;
    /*该设备所依附的master*/
	struct spi_master	*master;
    /*传输速率*/
	u32			max_speed_hz;
    /*该设备对应的片选*/
	u8			chip_select;
    /*数据传输模式(4种模式之一)*/
	u8			mode;
#define	SPI_CPHA	0x01			/* clock phase */
#define	SPI_CPOL	0x02			/* clock polarity */
#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */
#define	SPI_MODE_1	(0|SPI_CPHA)
#define	SPI_MODE_2	(SPI_CPOL|0)
#define	SPI_MODE_3	(SPI_CPOL|SPI_CPHA)
#define	SPI_CS_HIGH	0x04			/* chipselect active high? */
#define	SPI_LSB_FIRST	0x08			/* per-word bits-on-wire */
#define	SPI_3WIRE	0x10			/* SI/SO signals shared */
#define	SPI_LOOP	0x20			/* loopback mode */
#define	SPI_NO_CS	0x40			/* 1 dev/bus, no chipselect */
#define	SPI_READY	0x80			/* slave pulls low to pause */
    /*单次传输的bit数*/
	u8			bits_per_word;
    /*中断号*/
	int			irq;
    /*spi控制器相关的私有状态与私有数据等信息*/
	void			*controller_state;
	void			*controller_data;
    /*该设备的名称,用于设备与驱动的匹配检测*/
	char			modalias[SPI_NAME_SIZE];
    /*若使用gpio作为cs,此处标注cs的序号,*/
	int			cs_gpio;	/* chip select gpio */

	/*
	 * likely need more hooks for more protocol options affecting how
	 * the controller talks to each chip, like:
	 *  - memory packing (12 bit samples into low bits, others zeroed)
	 *  - priority
	 *  - drop chipselect after each word
	 *  - chipselect delays
	 *  - ...
	 */
};

        该结构体的定义还是比较明确的,通过上面总线的注册与注销的分析,spi device主要是借助设备模型的device_add接口,完成将该设备注册到spi总线上。spi设备的添加接口spi_add_device的流程图如下,也就是增加了spi_setup的调用设置该设备的通信模式与通信速率,然后就调用device_add完成spi设备的注册了。

下面我们看下spi device添加的方式,主要包括如下几个方式:

  1. 在spi master注册时,完成spi 设备的创建与注册(1. 通过设备树模式,为该master下的设备完成创建与注册 2.通过注册到board_list链表上的设备信息,完成spi设备的创建与注册);
  2. 针对spi master已创建的情况,通过调用spi_register_board_info接口,实现spi device的创建与注册操作。

 

针对spi device,其注销接口则主要是调用device_unregister完成设备的注销操作。

 

Spi driver的注册与注销

本小节主要进行spi driver的注册与注销操作,照例先分析spi driver的数据结构的定义。

 

	struct spi_driver {
	    /*主要用于说明该spi driver支持的设备,主要用于设备与驱动的匹配检测*/
		const struct spi_device_id *id_table;
	    /*探测、移除、电源管理相关的接口,可以理解为针对device_driver中对应函数的重载*/
		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);
	    /*定义该变量,以便使用系统的驱动模型及其接口,实现该spi driver注册到spi总线上*/
		struct device_driver	driver;
};

该接口基本上没有定义spi私有的相关信息,基本上是对device_driver类型的重载,因此spi driver的注册应该也不会复杂,基本上也就是调用driver_register实现驱动的注册。

 

spi_register_driver接口实现

如下为spi_register_driver的实现,设置该驱动属于spi总线、同时设置probe、remove等函数指针,最后调用driver_register完成spi总线的注册。

 

	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);
}

关于driver_register接口的实现流程,想详细了解的同学请查看之前写的设备驱动模型相关的文档,此处就不附上链接了,下图为driver_register以及device_register中实现设备与驱动匹配的简要流程(因spi总线未定义probe,因此当完成spi设备与驱动匹配检测后,则直接调用spi driver的probe进行探测操作)。

 

spi_unregister_driver接口

该接口直接调用driver_unregister,实现驱动的注销以及与device的解绑等操作,此接口为LINUX设备-总线-驱动模型中的接口,此处不再展开。

 

以上即为spi driver的注册与注销,相对来说还是比较简单的。那我们在实现一个spi驱动时,应该注意哪些事情呢:

  1. 确认该spi设备支持的通信方式、片选;
  2. 确认该spi设备驱动在进行探测时,需要对哪些寄存器进行初始化(针对初始化操作,在driver的probe接口中实现);
  3. 为了应用层程序可以和spi设备进行通信,提供字符设备文件(read、write、ioctl),并实现相应的操作接口(在driver的probe接口中实现字符设备的注册)。

以上即为实现一个spi device-driver的大致内容。

至此我们完成了spi master、spi device、spi driver模块的分析,针对大多数驱动工程师而言,只需要知道spi device、spi driver的注册与注销即可,而针对芯片公司而言,则需要完成spi master的驱动。

发布了140 篇原创文章 · 获赞 30 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/lickylin/article/details/103226472