据个人所知,Linux下SPI一直是处于被“忽略”的角色,市场上大部分板子在板级文件里面都没有关于SPI的相关代码(例如,mini2440),而大部分讲解驱动的的书籍也没有专门的一章来讲述关于Linux下SPI方面的内容(例如,宋宝华的Linux设备驱动开发详解)。与I2C相比,SPI就是一个不被重视的“家伙”,为什么?我也不甚了解。由于项目需要在UT4412BV01上移植SPI,查阅网络上几乎所有的SPI相关资料,都是对S3C2440和S3C6410的SPI驱动分析,而EXYNOS4412却只字不提,但仔细一想它们彼此之间是相通的,遂研究一番记于此,以便日后查阅之便。
1. SPI子系统架构详解
SPI总线上有两类设备:一类是主控端,通常作为SOC系统的一个子模块出现,比如很多嵌入式MPU中都常常包含SPI模块;一类是受控端,例如一些SPI接口的Flash、传感器等等。主控端是SPI总线的控制者,通过使用SPI协议主动发起SPI总线上的会话,而受控端则被动接受SPI主控端的指令,并作出相应的响应。
图还未画好,之后补上,补上后在一些写分析,哈哈。。。
2. 重要数据结构
(1)spi_master
struct spi_master用来描述一个SPI主控制器,我们一般不需要自己编写spi控制器驱动。
/**
* 结构体master代表一个SPI接口,或者叫一个SPI主机控制器,
* 一个接口对应一条SPI总线,master->bus_num则记录了这个总线号。
*/
struct spi_master {
struct device dev;
struct list_head list;
/**
* 总线编号,从零开始。
* 系统会用这个值去和系统中board_list链表中加入的每一个boardinfo结构中的每一个spi_board_info中的bus_num进行匹配,
* (每个boardinfo结构都是一个spi_board_info的集合,每一个spi_board_info都是对应一个SPI(从)设备的描述)
* 如果匹配上就说明这个spi_board_info描述的SPI(从)设备是链接在此总线上的,因此就会调用spi_new_device去创建一个spi_device。
*/
s16 bus_num;
/* 支持的片选的数量。从设备的片选号不能大于这个数.该值当然不能为0,否则会注册失败 */
u16 num_chipselect;
/* some SPI controllers pose alignment requirements on DMAable
* buffers; let protocol drivers know about these requirements.
*/
u16 dma_alignment;
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits;
/* other constraints relevant to this driver */
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 */
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
/* flag indicating that the SPI bus is locked for exclusive use */
bool bus_lock_flag;
int (*setup)(struct spi_device *spi); //根据spi设备更新硬件配置
/**
* 添加消息到队列的方法。此函数不可睡眠,其作用只是安排需要的传送,
* 并且在适当的时候(传送完成或者失败)调用spi_message中的complete方法,来将结果报告给用户。
*/
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
/*cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spidev的release函数*/
void (*cleanup)(struct spi_device *spi);
};
spi控制器的驱动在kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中声明和注册一个平台设备,然后在kernel3.0.15/driver/spi下面建立一个平台驱动。spi_master注册过程中会扫描kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中调用spi_register_board_info注册的信息,为每一个与本总线编号相同的信息建立一个spi_device。根据Linux内核的驱动模型,注册在同一总线下的驱动和设备会进行匹配。spi_bus_type总线匹配的依据是名字,这样当自己编写的spi_driver和spi_device同名的时候,spi_driver的probe方法就会被用,spi_driver就能看到与自己匹配的spi_device了。
(2)spi_device
struct spi_device用来描述一个SPI从设备。
/**
* 该结构体用于描述SPI设备,也就是从设备的相关信息。
* 注意:SPI子系统只支持主模式,也就是说SOC上的SPI只能工作在master模式,
* 外围设备只能为slave模式。
*/
struct spi_device {
struct device dev;
struct spi_master *master; //对应的控制器指针
u32 max_speed_hz; //spi传输时钟
u8 chip_select; //片选号,用来区分同一主控制器上的设备
u8 mode; //各bit的定义如下,主要是时钟相位/时钟极性
#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 */
u8 bits_per_word; //每个字长的比特数
int irq; //使用到的比特数
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE]; //spi是设备的名字
/*
* 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
* - ...
*/
};
(3)spi_driver
struct spi_driver用于描述SPI从设备驱动。驱动核心将根据driver.name和spi_board_info的modalias进行匹配,如果modalias和name相等,则绑定驱动程序和kernel3.0.5/arch/arm/mach-exynos/mach-smdk4x12.c中调用spi_register_board_info注册的信息对应的spi_device设备。它们的形式和struct platform_driver是一致的。
struct spi_driver {
const struct spi_device_id *id_table; //可以驱动的设备表,也就是说该驱动可以驱动一类设备
int (*probe)(struct spi_device *spi); //和spi_device匹配成功之后会调用这个方法.因此这个方法需要对设备和私有数据进行初始化
int (*remove)(struct spi_device *spi); //解除spi_device和spi_driver的绑定,释放probe申请的资源
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;
};
(4)
spi_transferstruct spi_transfer是对一次完整的数据传输的描述。每个spi_transfer总是读取和写入同样的长度的比特数,但是可以很容易的使用空指针舍弃读或写,为spi_transfer和spi_message分配的内存应该在消息处理期间保证是完整的。
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; //要写入设备的数据(必须是dma_safe),或者为NULL
void *rx_buf; //要读取的数据缓冲(必须是dma_safe),或者为NULL
unsigned len; //tx和tr的大小(字节数),这里不是指它的和,而是各自的长度,它们总是相等的
dma_addr_t tx_dma; //如果spi_message.is_dma_mapped是真,这个是tx的dma地址
dma_addr_t rx_dma; //如果spi_message.is_dma_mapped是真,这个是rx的dma地址
unsigned cs_change:1; //影响此次传输之后的片选,指示本次transfer结束之后是否要重新片选并调用setup改变设置
u8 bits_per_word; //每个字长的比特数,如果是0,使用默认值
u16 delay_usecs; //此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息
u32 speed_hz; //通信时钟,如果是0,使用默认值
struct list_head transfer_list; //用来连接的双向链表节点
};
(5)spi_message
struct spi_message就是对多个spi_transfer的封装。在消息需要传递的时候,会将spi_transfer通过自己的transfer_list字段挂到spi_message的transfers链表头上。spi_message用来原子的执行spi_transfer表示的一串数组传输请求。这个传输队列是原子的,这意味着在这个消息完成之前不会有其它消息占用总线。消息的执行总是按照FIFO的顺序,向底层提交spi_message的代码要负责管理它的内存空间。未显示初始化的内存需要使用0来初始化。为spi_transfer和spi_message分配的内存应该在消息处理期间保证是完整的。
struct spi_message {
struct list_head transfers; //此次消息的传输队列,一个消息可以包含多个传输段
struct spi_device *spi; //传输的目的设备
unsigned is_dma_mapped:1; //如果为真,此次调用提供dma和cpu虚拟地址
/* REVISIT: we might want a flag affecting the behavior of the
* last transfer ... allowing things like "read 16 bit length L"
* immediately followed by "read L bytes". Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/
/* completion is reported through a callback */
void (*complete)(void *context); //异步调用完成后的回调函数
void *context; //回调函数的参数
unsigned actual_length; //此次传输的实际长度
int status; //执行的结果,成功被置0,否则是一个负的错误码
/* 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;
};
(6)两个重要的板级结构
两个板级结构,其中spi_board_info用来初始化spi_device,s3c64xx_spi_info用来初始化spi_master。这两个板级的结构需要在移植的时候在kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中初始化。
spi_board_info(kernel3.0.15/linux/include/spi/spi.h)
/* 该结构也是对SPI从设备(spi_device)的描述,只不过它是板级信息,最终该结构的所有字段都将用于初始化SPI设备结构体spi_device */
struct spi_board_info {
/* the device name and module name are coupled, like platform_bus;
* "modalias" is normally the driver name.
*
* platform_data goes to spi_device.dev.platform_data,
* controller_data goes to spi_device.controller_data,
* irq is copied too
*/
char modalias[SPI_NAME_SIZE]; //spi设备名,会拷贝到spi_device的相应字段中.这是设备spi_device在SPI总线spi_bus_type上匹配驱动的唯一标识
const void *platform_data; //平台数据
void *controller_data;
int irq; //中断号
/* slower signaling on noisy or low voltage boards */
u32 max_speed_hz; //SPI设备工作时的波特率
/* bus_num is board specific and matches the bus_num of some
* spi_master that will probably be registered later.
*
* chip_select reflects how this chip is wired to that master;
* it's less than num_chipselect.
*/
u16 bus_num; //该SPI(从)设备所在总线的总线号,就记录了所属的spi_master之中的bus_num编号.一个spi_master就对应一条总线
u16 chip_select; //片选号.该SPI(从)设备在该条SPI总线上的设备号的唯一标识
/* mode becomes spi_device.mode, and is essential for chips
* where the default of SPI_CS_HIGH = 0 is wrong.
*/
u8 mode; //参考spi_device中的成员
/* ... may need additional spi_device chip config data here.
* avoid stuff protocol drivers can set; but include stuff
* needed to behave without being bound to a driver:
* - quirks like clock rate mattering when not selected
*/
};
s3c64xx_spi_info(kernel3.0.15/arch/arm/plat-samsung/include/plat/s3c64xx-spi.h)
struct s3c64xx_spi_info {
int src_clk_nr;
char *src_clk_name;
bool clk_from_cmu;
int num_cs; //总线上的设备数
int (*cfg_gpio)(struct platform_device *pdev);
/* Following two fields are for future compatibility */
int fifo_lvl_mask;
int rx_lvl_offset;
int high_speed;
int tx_st_done;
};
boardinfo是用来管理spi_board_info的结构,spi_board_info在板级文件中通过spi_register_board_info(struct spi_board_info const *info, unsigned n)交由boardinfo来管理,并挂到board_list链表上。
boardinfo(kernel3.0.15/drivers/spi/spi.c)
struct boardinfo {
struct list_head list; //用于挂到链表board_list上
struct spi_board_info board_info; //存放结构体spi_board_info
};
spi_register_board_info(kernel3.0.15/drivers/spi/spi.c)
int __init
spi_register_board_info(struct spi_board_info const *info, unsigned n)
{
struct boardinfo *bi;
int i;
bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
if (!bi)
return -ENOMEM;
for (i = 0; i < n; i++, bi++, info++) {
struct spi_master *master;
memcpy(&bi->board_info, info, sizeof(*info));
mutex_lock(&board_lock);
list_add_tail(&bi->list, &board_list);
list_for_each_entry(master, &spi_master_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
}
return 0;
}