Linux内核中SPI总线驱动分析

本文主要有两个大的模块:一个是SPI总线驱动的分析 (研究了具体实现的过程);

另一个是SPI总线驱动的编写(不用研究具体的实现过程)。 

1 SPI概述

      SPI是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口,是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。
      SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要4根线,事实上3根也可以。也是所有基于SPI的设备共有的,它们是SDI(数据输入),SDO(数据输出),SCLK(时钟),CS(片选)。
      MOSI(SDO):主器件数据输出,从器件数据输入。
      MISO(SDI):主器件数据输入,从器件数据输出。
      SCLK :时钟信号,由主器件产生。
      CS:从器件使能信号,由主器件控制。
      其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效,这就允许在同一总线上连接多个SPI设备成为可能。需要注意的是,在具体的应用中,当一条SPI总线上连接有多个设备时,SPI本身的CS有可能被其他的GPIO脚代替,即每个设备的CS脚被连接到处理器端不同的GPIO,通过操作不同的GPIO口来控制具体的需要操作的SPI设备,减少各个SPI设备间的干扰。
      SPI是串行通讯协议,也就是说数据是一位一位从MSB或者LSB开始传输的,这就是SCK时钟线存在的原因,由SCK提供时钟脉冲,MISO、MOSI则基于此脉冲完成数据传输。 SPI支持4-32bits的串行数据传输,支持MSB和LSB,每次数据传输时当从设备的大小端发生变化时需要重新设置SPI Master的大小端。

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来描述。

在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注销时候调用

2.3 SPI设备驱动层

     SPI设备驱动层为用户接口层,其为用户提供了通过SPI总线访问具体设备的接口。
      SPI设备驱动层可以用两个模块来描述,structspi_driver和struct spi_device。
      相关的数据结构如下:

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 {
    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];
}; 

        .modalias   = "m25p10",
        .mode   =SPI_MODE_0,   //CPOL=0, CPHA=0 此处选择具体数据传输模式
        .max_speed_hz    = 10000000, //最大的spi时钟频率
        /* Connected to SPI-0 as 1st Slave */
        .bus_num    = 0,   //设备连接在spi控制器0上
        .chip_select    = 0, //片选线号,在S5PC100的控制器驱动中没有使用它作为片选的依据,而是选择了下文controller_data里的方法。
       .controller_data = &smdk_spi0_csi[0],
通常来说spi_device对应着SPI总线上某个特定的slave。并且spi_device封装了一个spi_master结构体。spi_device结构体包含了私有的特定的slave设备特性,包括它最大的频率,片选那个,输入输出模式等等

3 OMAP3630 SPI控制器

     OMAP3630上SPI是一个主/从的同步串行总线,这边有4个独立的SPI模块(SPI1,SPI2,SPI3,SPI4),各个模块之间的区别在于SPI1支持多达4个SPI设备,SPI2和SPI3支持2个SPI设备,而SPI4只支持1个SPI设备。

SPI控制器具有以下特征:

    1.可编程的串行时钟,包括频率,相位,极性。

    2.支持4到32位数据传输

    3.支持4通道或者单通道的从模式

    4.支持主的多通道模式

        4.1全双工/半双工

        4.2只发送/只接收/收发都支持模式

        4.3灵活的I/O端口控制

        4.4每个通道都支持DMA读写

    5.支持多个中断源的中断时间

    6.支持wake-up的电源管理

    7.内置64字节的FIFO

4 spi_device以下一系列的操作是在platform板文件中完成!

spi_device的板信息用spi_board_info结构体来描述:
struct spi_board_info {
charmodalias[SPI_NAME_SIZE];
const void*platform_data;
void*controller_data;
intirq;
u32max_speed_hz;
u16bus_num;
u16chip_select;
u8mode;
};

这个结构体记录了SPI外设使用的主机控制器序号、片选信号、数据比特率、SPI传输方式等构建的操作是以下的两个步骤:
1.spi_device就构建并注册
static struct spi_board_info s3c_spi_devs[] __initdata = {
{
.modalias = "m25p10a",
.mode = SPI_MODE_0,
.max_speed_hz = 1000000,
.bus_num = 0,
.chip_select = 0,
.controller_data = &smdk_spi0_csi[SMDK_MMCSPI_CS],
},
};

2.
而这个info在init函数调用的时候会初始化:
spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));

在板文件中添加spi_board_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

(1) 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),
};
(2)spi_driver的注册
spi_register_driver(&m25p80_driver);//在有匹配的spi_device时,会调用m25p_probe

(3)实现probe操作
probe里完成了spi_transfer、spi_message的构建;
spi_message_init、spi_message_add_tail、spi_sync、spi_write_then_read函数的调用。

spi_transfer(里面集成了数据buf空间地址等信息)
spi_message(是spi_transfer的集合)的构建)
spi_message_init(初始化spi_message)
spi_message_add_tail(将新的spi_transfer添加到spi_message队列尾部)
spi_sync函数的调用(调用spi_master发送spi_message)
 spi_write_then_read(先写后读)

例如:

struct spi_transfer st={
 …………
};//填充spi_transfer
struct spi_message meg;//定义message
spi_init_message(&meg);//初始化meg
spi_message_add_tail(&st,&meg);//将st放在message队列尾部
Spi_sync(spi_device,&meg);//将message与spi_device关联,发送meg

static int m25p10a_read( struct m25p10a *flash, loff_t from,
        size_t len, char *buf )
{
    int r_count = 0, i;
    struct spi_transfer st[2];
    struct spi_message  msg;  

    spi_message_init( &msg );
    memset( st, 0, sizeof(st) );  

    flash->cmd[0] = CMD_READ_BYTES;
    flash->cmd[1] = from >> 16;
    flash->cmd[2] = from >> 8;
    flash->cmd[3] = from;  

    st[ 0 ].tx_buf = flash->cmd;
    st[ 0 ].len = CMD_SZ;
    spi_message_add_tail( &st[0], &msg );  

    st[ 1 ].rx_buf = buf;
    st[ 1 ].len = len;
    spi_message_add_tail( &st[1], &msg );  

    mutex_lock( &flash->lock );  

    /* Wait until finished previous write command. */
    if (wait_till_ready(flash)) {
        mutex_unlock( &flash->lock );
        return -1;
    }  

    spi_sync( flash->spi, &msg );
    r_count = msg.actual_length - CMD_SZ;
    printk( "in (%s): read %d bytes\n", __func__, r_count );
    for( i = 0; i < r_count; i++ ) {
        printk( "0x%02x\n", buf[ i ] );
    }  

    mutex_unlock( &flash->lock );  

    return 0;
}  

static int m25p10a_write( struct m25p10a *flash, loff_t to,
        size_t len, const char *buf )
{
    int w_count = 0, i, page_offset;
    struct spi_transfer st[2];
    struct spi_message  msg;
    write_enable( flash );  //写使能
        spi_message_init( &msg );
    memset( st, 0, sizeof(st) );  

    flash->cmd[0] = CMD_PAGE_PROGRAM;
    flash->cmd[1] = to >> 16;
    flash->cmd[2] = to >> 8;
    flash->cmd[3] = to;  

    st[ 0 ].tx_buf = flash->cmd;
    st[ 0 ].len = CMD_SZ;
  //填充spi_transfer,将transfer放在队列后面
    spi_message_add_tail( &st[0], &msg );
    st[ 1 ].tx_buf = buf;
    st[ 1 ].len = len;
    spi_message_add_tail( &st[1], &msg );
        spi_sync( flash->spi, &msg );   调用spi_master发送spi_message

    return 0;
} 

static int m25p10a_probe(struct spi_device *spi)
{
    int ret = 0;
    struct m25p10a  *flash;
    char buf[ 256 ];
    flash = kzalloc( sizeof(struct m25p10a), GFP_KERNEL );
    flash->spi = spi;
    /* save flash as driver's private data */
    spi_set_drvdata( spi, flash );
    memset( buf, 0x7, 256 );
    m25p10a_write( flash, 0, 20, buf); //0地址写入20个7
    memset( buf, 0, 256 );
    m25p10a_read( flash, 0, 25, buf ); //0地址读出25个数
    return 0;
}   

到目前为止,完成了SPI的驱动和应用。
时间: 2024-11-30 10:42:16

Linux内核中SPI总线驱动分析的相关文章

armv8(aarch64)linux内核中flush_dcache_all函数详细分析【转】

转自:http://blog.csdn.net/qianlong4526888/article/details/12062809 版权声明:本文为博主原创文章,未经博主允许不得转载.     /*  *  __flush_dcache_all() *  Flush the wholeD-cache.  * Corrupted registers: x0-x7, x9-x11  */ ENTRY(__flush_dcache_all) //保证之前的访存指令的顺序     dsb sy      

《Linux内核修炼之道》——分析内核源码如何入手?(上)

<Linux内核修炼之道>--分析内核源码如何入手?(上) 透过现象看本质,兽兽门无非就是一些人体艺术展示.同样往本质里看过去,学习内核,就是学习内核的源代码,任何内核有关的书籍都是基于内核,而又不高于内核的. 既然要学习内核源码,就要经常对内核代码进行分析,而内核代码千千万,还前仆后继的不断往里加,这就让大部分人都有种雾里看花花不见的无助感.不过不要怕,孔老夫子早就留给我们了应对之策:敏于事而慎于言,就有道而正焉,可谓好学也已.这就是说,做事要踏实才是好学生好同志,要遵循严谨的态度,去理解每

大话Linux内核中锁机制之原子操作、自旋锁【转】

转自:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多核间的相互竞争资源,单CPU之间的相互竞争,中断和进程间的相互抢占等诸多问题. 通常情况下,如图1所示,对于一段程序,我们的理想是总是美好的,希望它能够这样执行:进程1先对临界区完成操作,然

大话Linux内核中锁机制之RCU、大内核锁

大话Linux内核中锁机制之RCU.大内核锁 在上篇博文中笔者分析了关于完成量和互斥量的使用以及一些经典的问题,下面笔者将在本篇博文中重点分析有关RCU机制的相关内容以及介绍目前已被淘汰出内核的大内核锁(BKL).文章的最后对<大话Linux内核中锁机制>系列博文进行了总结,并提出关于目前Linux内核中提供的锁机制的一些基本使用观点. 十.RCU机制 本节将讨论另一种重要锁机制:RCU锁机制.首先我们从概念上理解下什么叫RCU,其中读(Read):读者不需要获得任何锁就可访问RCU保护的临界

Linux内核中常见内存分配函数【转】

 转自:http://blog.csdn.net/wzhwho/article/details/4996510 1.      原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表,如图2-1所示.四级页表分别为: l         页全局目录(Page Global Directory) l         页上级目录(Page Upper Directory) l         页中间

大话Linux内核中锁机制之原子操作、自旋锁

转至:http://blog.sina.com.cn/s/blog_6d7fa49b01014q7p.html 很多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多核间的相互竞争资源,单CPU之间的相互竞争,中断和进程间的相互抢占等诸多问题. 通常情况下,如图1所示,对于一段程序,我们的理想是总是美好的,希望它能够这样执行:进程1先对临界区完成操作,

大话Linux内核中锁机制之完成量、互斥量

大话Linux内核中锁机制之完成量.互斥量 在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题.  八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等待另一个执行单元完成某事后方可执行,它是一种轻量级机制.事实上,它即是为了完成进程间的同步而设计的,故而仅仅提供了代替同步信号量的一种解决方法,初值被初始化为0.它在include\linux\completion.h定义. 如图8.1所示,对于执行单元

Linux 串口、usb转串口驱动分析(2-2) 【转】

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26807463&id=4186852 Linux 串口.usb转串口驱动分析2 内核版本:2.6.35.6                                                                        荣鹏140319   声明:图和个别段落(我做了小的修改)是直接从网上截取   目标:主要是想对Linux 串口.usb转串

基本数据结构和算法在Linux内核中使用

基本数据结构和算法在Linux内核中使用 gaufunga day ago 搬运工 Linux内核(源代码的链接在github). 1.链表.双向链表.无锁链表. 2.B+ 树,这是一些你无法在教科书上找到的说明. 一个相对简单的B+树的实现.我把它作为一个学习练习来帮助理解B+树是如何工作的.这同样也被证明是有用的. ... 一个在教科书中并不常见的技巧.最小的值在右侧而不是在左侧.所有在一个节点里用到的槽都在左侧,所有没有用到的槽包含了空值(NUL).大多数操作只简单地遍历所有的槽一次并在第