一、SPI协议简介
SPI,即Serial Peripheral interface,是一种四线协议。SPI接口主要应用在 EEPROM,FLASH,实时时钟等。它是一种全双工,同步的通信总线。
它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少4根线,事实上3根也可以实现单向传输。四根线分别是:
(1)SCK----------------clock
(2)MOSI---------------master out slave in
(3)MISO---------------slave out master in
(4)CS-----------------chip select
CS是片选信号,只有片选信号为芯片的使能信号,才可以正常操作硬件进行通信。由此就运行1对多的通信方式了。
在基于SPI的线路中至少有一个主控设备,SCK信号线只由主设备控制,从设备不能控制信号线。SPI数据在按位传送的,一个时钟周期传输一个bit。这就是SCK时钟线存在的原因,由SCK提供时钟脉冲,MOSI和MISO则基于此脉冲完成数据传输。数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。因为SCK时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。也就是说,主设备通过对SCK时钟线的控制可以完成对通讯的控制。
SPI有4中工作方式:
(1)CPOL=0,CPHA=0
(2)CPOL=0,CPHA=1
(3)CPOL=1,CPHA=0
(4)CPOL=1,CPHA=1
SPI的输出串行同步时钟极性和相位可以配置,如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设音时钟相位和极性应该一致才能保证传输的数据是正确的。
二、内核SPI控制器
Kernel中使用SPI框架管理所有的SPI master & slave。与I2C一样,内核SPI框架不提供直接操作SPI master的方法,要操作SPI slave,相应的代码需构建为一个SPI slave驱动。在SPI slave驱动中,我们可以调用core中的API来控制master。
在实现一个master时,我们一般会作为platform driver注册到内核中,并且在probe中,调用SPI core的API初始化一个spi_master结构体,并且注册到API core中。
spi_alloc_master(...)
spi_register_master(...)
我们会赋值相关的回调函数到spi_master结构体中,以便在SPI core中去使用它。
struct spi_master中关键的回调函数为setup,transfer,cleanup三个。
一般我们会维护一个msg queue和一个内核线程workqueue来做spi数据的传送工作,在transfer中把数据加入的msg queue链表,并且使能一次workqueue,来在工作线程中传输,当传输结束后complete。
这样做的目的就是为了不阻塞在transfer函数中,因此达到异步传输的目的。
SPI slave
SPI slave驱动主要围绕struct spi_driver结构来实现,整个驱动的结构与platform driver相似。在probe成功之后,driver将拿到一个struct spi_device结构,内有关于这个关联slave设备的必要信息(模式/字长/时钟/匹配的master等),所有SPI操作的API调用均需要提供这个结构作为参数。SPI slave驱动使用struct 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;
};
struct spi_transfer {
const void *tx_buf;
void *rx_buf;
unsigned len;
dma_addr_t tx_dma;
dma_addr_t rx_dma;
unsigned cs_change:1;
u8 bits_per_word;
u16 delay_usecs;
u32 speed_hz;
struct list_head transfer_list;
};
每个struct spi_message主要包含一个struct spi_transfer的链表、spi_device的引用、complete回调,其中的spi_transfer链表用于描述数据传输,每个spi_transfer结构描述一次SPI数据传输,包含了input/output buffer、传输数据量等必要信息。
我们在slave驱动中进行数据传输时,会构建一个spi_transfer和一个spi_message结构体,并把spi transfer加入到spi message中,并最终传输出去。相关的API如下:
// 初始化spi_message结构, 主要是memset和链表头初始化. m指向的内存由调用者提供.
void spi_message_init(struct spi_message *m);
// 将t指向的spi_transfer结构添加到m内的链表末尾. 其实就是list_add_tail的封装.
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
// 移除已经入列的spi_transfer结构, 其实是list_del的封装.
// 注意只需提供spi_transfer即可.
void spi_transfer_del(struct spi_transfer *t);
// 一次申请完1个spi_ message + ntrans个spi_transfer,
// memset之, 并将所有ntrans个spi_transfer放入spi_ message的链表内.
struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags);
// 释放由spi_message_alloc申请的内存.
void spi_message_free(struct spi_message *m);
构建好spi_message后, driver就可以调用SPI core的API进行数据传输了:
int spi_setup(struct spi_device *spi);
//异步传输完成后spi_message内的complete回调会被调用(kworker的上下文)
int spi_async(struct spi_device *spi, struct spi_message *message);
int spi_sync(struct spi_device *spi, struct spi_message *message);