Linux主机驱动和外设驱动分离思想
SPI驱动总线架构:SPI核心层(x),SPI控制器驱动层(x),SPI设备驱动层(√)
2 Linux SPI驱动总体架构
在2.6的linux内核中,SPI的驱动架构可以分为如下三个层次:SPI 核心层、SPI控制器驱动层和SPI设备驱动层。
Linux 中SPI驱动代码位于drivers/spi目录。
2.1 SPI核心层
SPI核心层是Linux的SPI核心部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。
Linux中,SPI核心层的代码位于driver/spi/ spi.c。由于该层是平台无关层,本文将不再叙述,有兴趣可以查阅相关资料。
2.2 SPI控制器驱动层
SPI控制器驱动层,每种处理器平台都有自己的控制器驱动,属于平台移植相关层。它的职责是为系统中每条SPI总线实现相应的读写方法。在物理上,每个SPI控制器可以连接若干个SPI从设备。
在系统开机时,SPI控制器驱动被首先装载。一个控制器驱动用于支持一条特定的SPI总线的读写。一个控制器驱动可以用数据结构struct spi_master来描述。
3.SPI相关的数据结构
3.1 struct spi_master 用于描述一个SPI控制器
//在include/liunx/spi/spi.h文件中,在数据结构struct spi_master定义如下:
struct spi_master {
struct device dev;
s16 bus_num;
u16 num_chipselect;
int (*setup)(struct spi_device *spi);
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
void (*cleanup)(struct spi_device *spi);
};
bus_num为该控制器对应的SPI总线号。
num_chipselect 控制器支持的片选数量,即能支持多少个spi设备
setup函数是设置SPI总线的模式,时钟等的初始化函数, 针对设备设置SPI的工作时钟及数据传输模式等。在spi_add_device函数中调用。
transfer函数是实现SPI总线读写方法的函数。实现数据的双向传输,可能会睡眠
cleanup 注销的时候调用
3.2 SPI设备驱动层
SPI设备驱动层为用户接口层,其为用户提供了通过SPI总线访问具体设备的接口。
SPI设备驱动层可以用两个模块来描述,struct spi_driver和struct spi_device。
相关的数据结构如下:
struct spi_driver 用来描述一个SPI设备的驱动信息
struct spi_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);
struct device_driver driver;
};
Driver是为device服务的,spi_driver注册时会扫描SPI bus上的设备,进行驱动和设备的绑定,probe函数用于驱动和设备匹配时被调用。从上面的结构体注释中我们可以知道,SPI的通信是通过消息队列机制,而不是像I2C那样通过与从设备进行对话的方式。
struct spi_device 用来描述一个SPI总线上的从设备
通常来说spi_device对应着SPI总线上某个特定的slave。并且spi_device封装了一个spi_master结构体。
spi_device结构体包含了私有的特定的slave设备特性,包括它最大的频率,片选那个,输入输出模式等等
struct spi_device {
struct device dev;
struct spi_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 mode;
u8 bits_per_word;
int irq;
void *controller_state;
void *controller_data;
char modalias[32];
};
4 spi_device以下一系列的操作是在platform板文件中完成!
spi_device的板信息用spi_board_info结构体来描述:
spi_board_info参数
.modalias = "rc522", //初始化设备的名称
.platform_data = NULL,
.max_speed_hz = 10*1000*1000, //初始化传输速率
.bus_num = 2, //控制器编号
.chip_select = 0, //控制器片选的编号
.mode = SPI_MODE_0, //spi的模式 CPOL=0, CPHA=0 此处选择具体数据传输模式
.controller_data = &spi2_csi[0], //片选IO的信息
spi2_board_info设备描述结构体,设备注册函数spi_register_board_info
而这个info在init函数调用的时候会初始化:
spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));//注册spi_board_info。
这个代码会把spi_board_info注册到链表board_list上。
spi_device封装了一个spi_master结构体,事实上spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用scan_boardinfo扫描board_list,找到挂接在它上面的spi设备,然后创建并注册spi_device。
至此spi_device就构建并注册完成了!
5 spi_driver的构建与注册
driver有几个重要的结构体:spi_driver、spi_transfer、spi_message
driver有几个重要的函数 :spi_message_init、spi_message_add_tail、spi_sync
//spi_driver的构建
static struct spi_driver m25p80_driver = {
.driver = {
.name ="m25p80",
.bus =&spi_bus_type,
.owner = THIS_MODULE,
},
.probe = m25p_probe,
.remove =__devexit_p(m25p_remove),
};
//spidriver的注册
spi_register_driver(&m25p80_driver);
在有匹配的spi_device时,会调用m25p_probe
probe里完成了spi_transfer、spi_message的构建;
spi_message_init、spi_message_add_tail、spi_sync、spi_write_then_read函数的调用
在SPI总线上是通过封装一系列的spi_transfer到一个spi_message中,然后将spi_message提交到SPI子系统去。
下面是spi_transfer结构:
struct spi_transfer {
const void*tx_buf; //驱动提供的发送缓冲区dma,
void *rx_buf; //接收缓冲区
unsigned len; //长度一般是8位
dma_addr_ttx_dma; //发送dma,controller使用
dma_addr_t rx_dma; //接收dma
unsigned cs_change:1; //片选位
u8 bits_per_word; //每字长度
u16 delay_usecs; //延迟
u32 speed_hz; //速度
struct list_headtransfer_list; //transfer 链表
};
在spi_transfer中时常更改的域也许只有len,tx_buf和rx_buf。剩下的当以0来初始化。
单个spi_transfer可表示一次读,一次写或者是一次读写。在SPIcontroller驱动下,所有操作常是全双工的。向spi_transfer中rx_buf传递一个NULL,这就是一次只写操作,会丢弃MISO线上的数据。同样向tx_buf传递一个NULL,这就是一次只读操作了。spi_transfer中len域代表(已经多少字节数据流过总线了)howmany bytes to clock the bus。
spi_message结构:
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
unsigned is_dma_mapped:1;
void (*complete)(void*context);
void *context;
unsigned actual_length;
int status;
struct list_head queue;
void *state;
};
transfer这个spi_message所包含有的spi_transfer链表头。
is_dma_mappedspi_transfer中tx_dma和rx_dma是否已经mapped。
complete回调函数
context 提供给complete的可选参数
actual_lengthspi_message已经传输了的字节数
status 出错与否,错误时返回errorcode
queue 和state 供controller驱动内部使用
在每次使用spi_message可以使用函数void spi_message_init(structspi_message *m);来初始化。
向spi_message添加transfers可以使用spi_message_add_tail()函数:
void spi_message_add_tail(structspi_transfer *t, struct spi_message *m);
一旦你准备好了spi_message,就可以使用spi_async()来向SPI系统提交了:
int spi_async(struct spi_device *spi,struct spi_message *message);
因为是异步的,一提交就立马返回了,这也就是说需要同步机制(complete就是了)。他确保不会睡眠,可安全的在中断handler或其他不可休眠的代码中调用。稍后会念念他的好的。
使用spi_async()需要注意的是,在complete()未返回前不要轻易访问你一提交的spi_transfer中的buffer。也不能释放SPI系统正在使用的buffer。一旦你的complete返回了,这些buffer就又是你的了。
使用完成回调机制稍显复杂,可以使用SPI系统提供的另一个同步版本:spi_sync():
int spi_sync(struct spi_device *spi,struct spi_message *message);
因为是同步的,spi_sync提交完spi_message后不会立即返回,会一直等待其被处理。一旦返回就可以重新使用buffer了。spi_sync()在drivers/spi/spi.c中实现,其调用了spi_async(),并休眠直至complete返回。
1 #include <linux/init.h>
2 #include <linux/module.h>
3 #include <linux/ioctl.h>
4 #include <linux/fs.h>
5 #include <linux/device.h>
6 #include <linux/err.h>
7 #include <linux/list.h>
8 #include <linux/errno.h>
9 #include <linux/mutex.h>
10 #include <linux/slab.h>
11 #include <linux/compat.h>
12 #include <linux/spi/spi.h>
13 #include <linux/spi/spidev.h>
14 #include <asm/uaccess.h>
15 #include <linux/gpio.h>
16 #include <mach/gpio.h>
17 #include <plat/gpio-cfg.h>
18 #include <linux/delay.h>
19 #include <linux/miscdevice.h>
20
21 struct spi_device *my_spi;
22
23 #define RC522_RESET_PIN EXYNOS4_GPK1(0)
24
25 void my_rc522_reset() // 驱动初始化 (IO部分)
26 {
27 //printk("************************ %s\n", __FUNCTION__);
28 if(gpio_request_one(RC522_RESET_PIN, GPIOF_OUT_INIT_HIGH, "RC522_RESET"))
29 pr_err("failed to request GPK1_0 for RC522 reset control\n");
30
31 s3c_gpio_setpull(RC522_RESET_PIN, S3C_GPI