Linux内核设备驱动:SPI驱动

本文介绍了Linux内核中的SPI驱动架构,分为SPI核心层、控制器驱动层和设备驱动层。SPI核心层提供API,控制器驱动层实现读写方法,设备驱动层为用户提供访问设备的接口。文章详细阐述了各层次的职责、主要数据结构和关键函数,重点讲解了SPI设备驱动的构建与注册过程。
摘要由CSDN通过智能技术生成

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
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值