当前位置:   article > 正文

Linux SPI总线设备驱动模型详解_linux-2.6spi驱动代码

linux-2.6spi驱动代码

转载自:http://blog.csdn.net/u014106791/article/details/52301671




<script type="text/javascript">
    var username = "u014106791";var _blogger = username;var blog_address = "/u014106791";var static_host = "http://static.blog.csdn.net";
    var currentUserName = ""; var fileName = '52301671';var commentscount = 0; var islock = false
    window.quickReplyflag = true;
    var totalFloor=0;

             var isBole = false;


             var isDigg = false;


         var isExpert=false;


    var isAdm=false;


</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19










<script src="//static.blog.csdn.net/public/switchHome/switchHome.js?v=2017.044"></script>   

<script type="text/javascript" src="http://static.blog.csdn.net/Skin/skin3-template/fonts/iconfont.js"></script>
<script src="//csdnimg.cn/rabbit/exposure-click/main.js?v1.15.23"></script>
<script type="text/javascript" src="http://c.csdnimg.cn/pubfooter/js/tracking_for_recommend.js?v=0911" charset="utf-8"></script>
<script type="text/javascript" src="http://csdnimg.cn/pubfooter/js/tracking.js" charset="utf-8"></script>
<script type="text/javascript" src="http://static.blog.csdn.net/scripts/cnick.js" charset="utf-8"></script>

<link rel="stylesheet" href="http://static.blog.csdn.net/code/prettify.css" />
<script type="text/javascript" src="http://static.blog.csdn.net/code/prettify.js"></script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
<script type="text/javascript">

    // Traffic Stats of the entire Web site By baidu
    var _hmt = _hmt || [];
    (function() {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?6bcd52f51e9b3dce32bec4a3997715ac";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
    })();
    // Traffic Stats of the entire Web site By baidu end



</script>
<meta name="description" content="随着技术不断进步,系统的拓扑结构越来越复杂,对热插拔、跨平台移植性的要求越来越高,早期的内核难以满足这些要求,从linux2.6内核开始,引入了总线设备驱动模型。其实在linux2.4总线的概念就已经提出来了,直到2.6版本的内核才运用。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Linux系统中有很多条总线,如I2C、USB、platform、PCI等。
以spi为例,假如有M种不同类型CPU,N中不同SPI外设,在写裸机驱动的时候” />


<title>Linux SPI总线设备驱动模型详解 - CSDN博客</title>
  • 1


Linux SPI总线设备驱动模型详解

原创 2016年08月24日 16:42:10
            <ul class="article_tags clearfix csdn-tracking-statistics" data-mod="popu_377">
                <li class="tit">标签:</li>

            </ul>
            <ul class="right_bar">
                <li><button class="btn-noborder"><i class="icon iconfont icon-read"></i><span class="txt">1906</span></button></li>
                <!--<li><button class="btn-noborder"><i class="icon iconfont icon-dashang-"></i><span class="txt">0</span></button></li>-->

                <li class="edit">
                    <a class="btn-noborder" href="" >
                        <i class="icon iconfont icon-bianji"></i><span class="txt">编辑</span>
                    </a>
                </li>
                <li class="del">
                    <a class="btn-noborder" onclick="javascript:deleteArticle(fileName);return false;">
                        <i class="icon iconfont icon-shanchu"></i><span class="txt">删除</span>
                    </a>
                </li>
            </ul>
        </div>
        <div id="article_content" class="article_content csdn-tracking-statistics" data-mod="popu_519" data-dsm="post">
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

随着技术不断进步,系统的拓扑结构越来越复杂,对热插拔、跨平台移植性的要求越来越高,早期的内核难以满足这些要求,从linux2.6内核开始,引入了总线设备驱动模型。其实在linux2.4总线的概念就已经提出来了,直到2.6版本的内核才运用。

Linux系统中有很多条总线,如I2C、USB、platform、PCI等。

以spi为例,假如有M种不同类型CPU,N中不同SPI外设,在写裸机驱动的时候,M种CPU驱动同一个外设需要M份代码,而N种外设使用同一个cpu又需要N份代码,所以需要M*N份代码,这是典型的高内聚低耦合架构。


这种网状的拓扑结构是不符合人的逻辑思维的,将M*N种耦合变成M+1+N中耦合,将大大减少linux移植工作。


在系统中抽象出一条SPI总线,然后总线中(总线注册的那个文件 spi.c 和spi.h,I2C总线注册是i2c-core.c和i2c-core.h)包含SPI控制器抽象结构体spi_master等,spi控制器和外设之间交互采用spi总线提供的标准api来进行,控制器设备和外设驱动填充相关结构体。

试想一下usb,当我们把鼠标或者键盘插入电脑时,是不是会有个驱动加载的过程?这就是在寻找总线上的驱动。总线有一种义务,就是感知设备在总线上的挂载和卸载,同时有义务去寻找与设备匹配的驱动。我们的spi也一样,当有外设挂载到spi总线上的时候,就会寻找总线上所有的驱动与之匹配,匹配成功,则由该驱动服务这个设备。反过来,总线有义务感知驱动在总线上的挂载和卸载,当驱动挂载到总线时,会寻找与之匹配的设备,该驱动就服务于匹配的设备。

总线在内核中的抽象

在linux内核中,总线由bus_type结构描述,定义在linux/device.h中。

struct bus_type { 
const char name; /总线名称*/
int (*match) (struct device *dev, struct
device_driver drv); /驱动与设备的匹配函数*/
………
}
  • 1
  • 2
  • 3
  • 4
  • 5
主要关注match函数,当有一个设备挂载到一条总线上的时候,总线要把这个设备和挂载到这条总线上的驱动一一进行匹配,匹配的函数就是这个match指针。


总线的注册与注销

注册:bus_register(struct bus_type *bus)若成功,新的总线将被添加进系统,并可在/sys/bus 下看到相应的目录。

注销:void bus_unregister(struct bus_type *bus)。

进入到板子的/sys/bus目录,ls一下,可以看到系统所有的总线。


随便进入一个目录,如SPI目录


Devices目录表示这条总线上所有挂载的设备。Drivers目录表示这条总线上所有的设备。

下面以一个示例来注册一条总线到系统中,一般情况下,是不需要另外添加总线到设备中的。添加的总线名字叫my_bus,加载驱动之后,会在/sys/bus目录下看到一个my_bus目录。

新建bus.c:

#include <linux/init.h>

    #include <linux/module.h> #include <linux/kernel.h> #include <linux/device.h> int my_match(struct device *dev, struct device_driver *drv) { printk(“my_match was run\n”); return !strncmp(dev->kobj.name,drv->name,strlen(drv->name)); } struct bus_type my_bus_type = { .name = “my_bus”,//总线名称 .match = my_match,//驱动与设备匹配函数 }; EXPORT_SYMBOL(my_bus_type); static int my_bus_init() { int ret; ret = bus_register(&my_bus_type); return ret; } static void my_bus_exit() { bus_unregister(&my_bus_type); } module_init(my_bus_init); module_exit(my_bus_exit); MODULE_LICENSE(“GPL”);首先,总线也是内核的一个模块,我们把它编译成.ko的方式加载到内核,总线的名字是”my_bus”,总线的匹配函数是my_match,当总线上的驱动和设备都挂载上去时,会调用my_match函数进行配对,配对也很简单,就是对比驱动和设备名字是否相同。返回非0表示my_match匹配成功,返回0表示匹配失败。

    EXPORT_SYMBOL(my_bus_type);将my_bus_type结构导出给外部文件用,因为设备和驱动都需要指明要挂载到哪条总线上。

    编写Makefile,使之生成.ko模块,加载bus.ko,然后在/sys/bus目录下会生成my_bus目录

    驱动描述结构

    在 Linux内核中, 驱动由 device_driver结构表示。

    struct device_driver {
    {
      const char *name; /*驱动名称*/
      struct bus_type *bus; /*驱动程序所在的总线*/
      int (*probe) (struct device *dev);
      ………
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    Name表示驱动的名字;bus表示驱动要挂载到哪条总线上,待会儿将挂载到刚刚创建的my_bus总线上;probe表示驱动和设备匹配成功之后要运行的函数。

    驱动的注册与注销:

    驱动的注册使用:int driver_register(struct device_driver *drv)

    驱动的注销使用:void driver_unregister(struct device_driver *drv)

    接下来编写driver.c文件,编译成模块,将驱动加载到内核并挂载到my_bus总线上。

    #include <linux/init.h>
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    #include <linux/module.h> #include <linux/kernel.h> #include <linux/device.h> extern struct bus_type my_bus_type; int my_probe(struct device *dev) { printk("driver found the devicre it can handle\n"); return 0; } struct device_driver my_driver = { .name = "yty",//驱动名字 .bus = &my_bus_type,//属于哪条总线 .probe = my_probe, }; static int my_driver_init() { return driver_register(&my_driver); } static int my_driver_exit() { driver_unregister(&my_driver); } module_init(my_driver_init); module_exit(my_driver_exit); MODULE_LICENSE("GPL");

    驱动的名字叫“yty”,属于bus.c中的my_bus_type这条总线,驱动和设备匹配成功之后,就会运行my_probe函数,也就是会打印出”driver found the devicre it can handle\n”信息。

    编译成.ko文件,然后insmod,在/sys/bus/my_bus/drivers目录下就生成了yty目录。


    设备描述结构

    在 Linux内核中, 设备由struct device结构表示。

    struct device {
    {
    const char *init_name; /*设备的名字*/
    struct bus_type *bus; /*设备所在的总线*/
    ………
    }
    • 1
    • 2
    • 3
    • 4
    • 5

    设备的注册与注销

    设备的注册使用int device_register(struct device *dev)

    设备的注销使用:void device_unregister(struct device *dev)

    编写device.c文件:

    #include <linux/init.h>
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    #include <linux/module.h> #include <linux/kernel.h> #include <linux/device.h> extern struct bus_type my_bus_type; struct device my_dev = { .init_name = "yty", .bus = &my_bus_type }; static int my_device_init() { int ret; ret = device_register(&my_dev); return ret; } static void my_device_exit() { device_unregister(&my_dev); } module_init(my_device_init); module_exit(my_device_exit); MODULE_LICENSE("GPL");

    .init_name要和驱动的.那么一样,要不然匹配不上,.bus仍然是要属于my_bus总线。

    编译并加载.ko文件,然后会出现如下打印:


    由图可知,当挂载设备到my_bus总线上时,先调用总线上的my_match函数,然后驱动来处理这个设备,驱动中的my_probe就运行了。

    总线的感性认识就到此结束了。

    SPI总线设备驱动分析

    在sourceInsight中打开内核代码drivers/spi/spi.c文件,然后分析。


    在spi_init函数中,调用了bus_register注册一条总线,总线的名字叫做spi,spi_bus_type结构就是我们需要关注的,顺便看看.match。


    看内核代码挑重要的看,不要每一行都看,直接跳到strcmp函数去,可以知道总线上驱动和设备的配备是通过比较驱动和设备的名字。如果有多个相同的设备,那么就应该定义.id了,靠id来区别我这个驱动到底是服务哪个设备。

    总线的注册就讲解完毕。在spi.c中,提供了注册设备和注册驱动的标准api、提供了spi收发函数、spi初始化函数等。可以理解为spi总线向我们提供了标准的API接口。

    以系统提供的范例spidev.c为例:


    我们知道,在注册一个spi驱动是调用系统给我们提供的函数-spi_register_driver,这个标准的api也是由spi.c提供给我们的。通过sourceInsight跳转到spi_register_driver函数,这个函数就在spi.c中。


    由前面的范例代码知道,注册一个驱动使用driver_register。

    sdrv->driver.bus = &spi_bus_type;表示这个驱动属于spi这条总线。另外spidev中的probe,remove都通过指针传到了spi_register_driver函数中。设备和驱动匹配成功,调用spi_drv_probe,它经过赋值之后,是指向spidev.c中的spidev_probe。在spi通用外设驱动spidev.c中,调用spi_async来实现发送和接收数据的,spi_async也是由spi.c提供的,即”总线提供标准API”。

    Spi设备挂载分析:

    添加外设之后,一般都是需要修改板级逻辑的,使用spi通用驱动也不例外。在borad-sam9x5ek.c中要添加。其它cpu类似。


    在ek_board_init中调用了at91_add_device_spi函数,将设备注册到系统。


    用sourceInsight继续追踪该函数。at91_add_device_spi调用spi_register_board_info 调用spi_register_board_info。spi_register_board_info这个函数就是在spi.c中,也就是说,总线提供标准的API注册设备到总线上。这个API其实最终还是调用device_register将设备注册到总线上。

    接下来看看spi_async是如何访问到spi相关寄存器的。追踪spi_async,spi_async调用__spi_async,然后调用return master->transfer(spi, message);也就是调用master的transfer指针函数,这个函数在哪里被赋值了呢?

    找到atmel_spi.c文件。S3c6410板子是spi_s3c64xx.c。然后找到probe函数,atmel是atmel_spi_probe。就会看到如下代码:


    spi_alloc_master也是spi总线提供的标准API,用于申请一个spi_master结构,然后对这个结构初始化,所以spi_async将调用atmel_spi_transfer,然后我们进一步追踪代码,atmel_spi_transfer调用atmel_spi_next_message调用atmel_spi_next_xfer 调用atmel_spi_next_xfer_pio,atmel_spi_next_xfer_pio函数就是真正读写寄存器的操作了。访问寄存器不能直接写哦,需要iomap哦,而且要采用专门的读写函数,如readl、readb、writel、writeb、spi_wrtel等。


    假如有多个控制器,那么外设怎么和某个控制器建立关系呢?这个任务是由板级逻辑来联系的。就以刚刚的spidev板级代码来说


    max_speed_hz是说明我这个spidev外设,需要使控制器100万Hz的时钟频率,bus_num说明说明spidev外设需要使用spi0控制器。

    总结:

    SPI,I2C,USB等采用总线的方式,将主机驱动和外设驱动分离,这样就涉及到四个软件模块:

    1.主机端的驱动。根据具体的cpu芯片手册操作IIC、SPI、USB等寄存器,产生各种波形。主机端驱动大部分由原厂实现好。

    2.连接主机和外设的纽带。外设驱动不直接调用主机端的驱动来产生波形,而是调用一个标准的API,由这个标准的API把这个波形的传输请求间接转发给了具体的主机端驱动。

    3.外设端驱动。外设挂载到IIC、SPI、USB等总线上,我们在probe()函数中去注册它的具体类型(I2C,SPI,USB等类型),当要去访问外设的时候,就调用标准的API。如SPI读写函数spi_async,I2C读写函数:i2c_smbus_read_byte i2c_smbus_write_byte 等。

    4.板级逻辑。板级逻辑用来描述主机和外设如何联系在一起的,假如cpu有多个SPI控制器,cpu又接有多个SPI外设,那究竟用哪个SPI控制器去控制外设?这个管理属于板级逻辑的责任。如board-sam9x5ek.c中:.bus_num= 0,表示用SPI0去控制spi通用外设驱动spidev。

    版权声明:本文为博主原创文章,转载请注明文章出处。
    • 本文已收录于以下专栏:

    相关文章推荐

    Linux驱动修炼之道-SPI驱动框架源码分析(中)

    来自:http://blog.csdn.net/woshixingaaa/article/details/6574220 这篇来分析spi子系统的建立过程。 嵌入式微处理器访问SPI设备有…
    • lanmanck
    • lanmanck
    • 2011-10-21 22:10
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/168709?site
    推荐阅读
    相关标签
      

    闽ICP备14008679号