本篇作为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框架的区别
- 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的绑定。
- i2c 驱动模块提供的通用字符设备,且该字符设备是与i2c adapter有所关联的(每一个i2c adapter均对应一个i2c 字符设备,通过该i2c字符设备即可访问该adapter下所有的i2c设备);而spi 通用字符设备则可以理解是为具体挂载在某一个master下的spi设备而创建的(因spi 通用字符设备的片选即确定了一个spi 设备);
- spi驱动模块创建了spi master class,系统中所有注册的master device均链接至该class下。
spi驱动-总线-控制器-设备模块之间的关联
这四个子模块通过借助LINUX设备-总线-驱动模型提供的数据结构以及接口函数,完成了子模块间的关联(这四个子模块关联关系的建立以及解除,即通过这几个模块间的注册与注销接口完成的)。我们下面先简要说下这几个模块间的关联,然后再分别对这几个模块进行分析说明(spi总线模块在之前已经分析过)。下面通过一张简略图描述这四者之间的关系。
- spi总线通过其bus_kset成员,链接至系统的总线集bus_kset上;
- spi中心概念的klist_devices、klist_drivers链表,完成了总线与spi设备、spi驱动之间的关联;
- Spi master借助device类型变量中的父子链表成员,完成与spi device的关联(父子关系关联);
- Spi master与spi device借助其spi模块定义的成员,完成了spi master与spi device的绑定;
- Spi master、spi device借助其device类型,完成了与系统设备集devices_kset的关联;
- Spi master借助其device类型成员变量,完成与spi_master_class类的关联(此图未画出)。
基本上通过这些关联关系,即完成了spi框架中这几个子模块间的关联操作。
spi master device的注册与注销以及相关结构体说明
Spi master用于抽象一个spi控制器,该控制器提供spi总线的通信方法等信息,同时该spi控制器的device类型的成员变量是该控制器下所有spi device的父设备。下面我们分析下该控制器的注册与注销
struct spi_master 分析
我们先分析下spi master的定义,对该结构体变量熟悉了以后,对它的的注册与注销也就基本上熟悉了。我们对主要成员进行说明:
- dev用于使用LINUX设备-总线-驱动模型;
- Bus_num、num_chipselect用于说明总线号及支持的cs个数;
- mode_bits表示该控制器支持的模式(即spi总线支持的四种模式);
- setup接口用于设置总线模式、时钟等内容;
- transfer说明该控制器提供的通信方法(目前在新的内核中已不推荐使用该接口,推荐使用队列方式,队列模式由spi核心提供,我们会在后面进行详细说明);
- kworker、kworker_task、queued、pump_messages、prepare_transfer_hardware、unprepare_transfer_hardware、unprepare_transfer_hardware这几个成员变量均是用于spi核心提供的队列通信模式。
- 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注册接口的流程图如下,主要实现的功能如下:
- 调用device_add,将该master对应的device成员注册到系统的设备集devices_kset上,以及链接至spi_master_class类,此处主要是使用系统的设备模型对应的接口;
- 针对新版内核,spi模块提供队列传输模式,为该master创建worker kthread,并与master的进行绑定(该部分在后面会进行详细说明);
- 将该master链接到系统的spi_master_list链表上(该变量主要是在调用spi模块的设备注册接口时,根据传递的bus_num与该链表上的master进行匹配检测,从而实现将注册的spi device与对应的master的绑定);
- 对系统上board_list链表上注册的spi board info(尚未创建对应的spi device),若其属于该master,则为其创建spi device并注册到spi总线上,同时完成spi device与该master的绑定操作;
- 对于通过设备树创建的master,针对该master的of_node节点上所有的设备子节点,均创建对应的spi device,并将其注册到spi总线上,同时完成spi device与master的绑定操作。
针对spi device,其父设备均为其所绑定的spi master。
Spi master的注销
以上为spi master注册相关的处理流程分析,而针对spi master注销的流程,其主要流程与上
面的流程刚发相反,主要内容如下:
- 将该master从spi_master_list链表上移除;
- 针对该spi master上所有的子设备,调用device_unregister将其从spi总线上移除,并完成与spi driver的解绑;
- 若该spi master支持队列传输模式,则关闭该master对应的worker kthread。
- 针对该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添加的方式,主要包括如下几个方式:
- 在spi master注册时,完成spi 设备的创建与注册(1. 通过设备树模式,为该master下的设备完成创建与注册 2.通过注册到board_list链表上的设备信息,完成spi设备的创建与注册);
- 针对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驱动时,应该注意哪些事情呢:
- 确认该spi设备支持的通信方式、片选;
- 确认该spi设备驱动在进行探测时,需要对哪些寄存器进行初始化(针对初始化操作,在driver的probe接口中实现);
- 为了应用层程序可以和spi设备进行通信,提供字符设备文件(read、write、ioctl),并实现相应的操作接口(在driver的probe接口中实现字符设备的注册)。
以上即为实现一个spi device-driver的大致内容。
至此我们完成了spi master、spi device、spi driver模块的分析,针对大多数驱动工程师而言,只需要知道spi device、spi driver的注册与注销即可,而针对芯片公司而言,则需要完成spi master的驱动。