zoukankan      html  css  js  c++  java
  • Linux驱动

    通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动、通用接口层和控制器驱动三大部分。其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:
    1.    申请必要的硬件资源,例如中断,DMA通道,DMA内存缓冲区等等;
    2.    配置SPI控制器的工作模式和参数,使之可以和相应的设备进行正确的数据交换工作;

    3.    向通用接口层提供接口,使得上层的协议驱动可以通过通用接口层访问控制器驱动;

    4.    配合通用接口层,完成数据消息队列的排队和处理,直到消息队列变空为止;

    /*****************************************************************************************************/
    声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
    /*****************************************************************************************************/

    定义控制器设备


    SPI控制器遵循linux的设备模型框架,所以,一个SPI控制器在代码中对应一个device结构,对于嵌入式系统,我们通常把SPI控制器作为一个平台设备来对待,所以,对于我们来说,只要在板级的代码中为SPI控制器定义一个platform_device结构即可。下面以Samsung的SOC芯片:S3C6410,做为例子,看看如何定义这个platform_device。以下的代码来自:/arch/arm/plat-samsung/devs.c中:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static struct resource s3c64xx_spi0_resource[] = {  
    2.     [0] = DEFINE_RES_MEM(S3C_PA_SPI0, SZ_256),  
    3.     [1] = DEFINE_RES_DMA(DMACH_SPI0_TX),  
    4.     [2] = DEFINE_RES_DMA(DMACH_SPI0_RX),  
    5.     [3] = DEFINE_RES_IRQ(IRQ_SPI0),  
    6. };  
    7.   
    8. struct platform_device s3c64xx_device_spi0 = {  
    9.     .name       = "s3c6410-spi",  
    10.     .id     = 0,  
    11.     .num_resources  = ARRAY_SIZE(s3c64xx_spi0_resource),  
    12.     .resource   = s3c64xx_spi0_resource,  
    13.     .dev = {  
    14.         .dma_mask       = &samsung_device_dma_mask,  
    15.         .coherent_dma_mask  = DMA_BIT_MASK(32),  
    16.     },  
    17. };  

    由此可见,在这个platform_device中,我们定义了控制器所需的寄存器地址、DMA通道资源和IRQ编号,设备的名字定义为:s3c64xx-spi,这个名字用于后续和相应的控制器驱动相匹配。在machine的初始化代码中,我们需要注册这个代表SPI控制器的平台设备,另外,也会通过s3c64xx_spi0_set_platdata函数设置平台相关的参数供后续的控制器驱动使用:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static struct platform_device *crag6410_devices[] __initdata = {  
    2.         ......  
    3.         &s3c64xx_device_spi0,  
    4.         ......  
    5. };  
    6.   
    7. static void __init xxxx_machine_init(void)  
    8. {  
    9.   
    10.         s3c64xx_spi0_set_platdata(NULL, 0, 2);  
    11.         //注册平台设备  
    12.         platform_add_devices(crag6410_devices, ARRAY_SIZE(crag6410_devices));  
    13. }  

    s3c64xx_spi0_set_platdata函数的定义如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. void __init s3c64xx_spi0_set_platdata(int (*cfg_gpio)(void), int src_clk_nr,  
    2.                         int num_cs)  
    3. {  
    4.     struct s3c64xx_spi_info pd;  
    5.     ......  
    6.     pd.num_cs = num_cs;  
    7.     pd.src_clk_nr = src_clk_nr;  
    8.     pd.cfg_gpio = (cfg_gpio) ? cfg_gpio : s3c64xx_spi0_cfg_gpio;  
    9.         ......  
    10.     s3c_set_platdata(&pd, sizeof(pd), &s3c64xx_device_spi0);  
    11. }  

    上述函数主要是指定了控制器使用到的gpio配置、片选引脚个数和时钟配置等信息。这些信息在后面的控制器驱动中要使用到。

    注册SPI控制器的platform_driver

    上一节中,我们把SPI控制器注册为一个platform_device,相应地,对应的驱动就应该是一个平台驱动:platform_driver,它们通过platform bus进行相互匹配。以下的代码来自:/drivers/spi/spi-s3c64xx.c

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static struct platform_driver s3c64xx_spi_driver = {  
    2.         .driver = {  
    3.                 .name   = "s3c64xx-spi",  
    4.                 .owner = THIS_MODULE,  
    5.                 .pm = &s3c64xx_spi_pm,  
    6.                 .of_match_table = of_match_ptr(s3c64xx_spi_dt_match),  
    7.         },  
    8.         .remove = s3c64xx_spi_remove,  
    9.         .id_table = s3c64xx_spi_driver_ids,  
    10. };  
    11. MODULE_ALIAS("platform:s3c64xx-spi");  
    12.   
    13. static int __init s3c64xx_spi_init(void)  
    14. {  
    15.         return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);  
    16. }  
    17. subsys_initcall(s3c64xx_spi_init);  

    显然,系统初始化阶段(subsys_initcall阶段),通过s3c64xx_spi_init(),注册了一个平台驱动,该驱动的名字正好也是:s3c64xx-spi,自然地,平台总线会把它和上一节定义的platform_device匹配上,并且触发probe回调被调用(就是s3c64xx_spi_probe函数)。当然,这里的匹配是通过id_table字段完成的:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static struct platform_device_id s3c64xx_spi_driver_ids[] = {  
    2.         {  
    3.                 .name           = "s3c2443-spi",  
    4.                 .driver_data    = (kernel_ulong_t)&s3c2443_spi_port_config,  
    5.         }, {  
    6.                 .name           = "s3c6410-spi",  
    7.                 .driver_data    = (kernel_ulong_t)&s3c6410_spi_port_config,  
    8.         },   
    9.         ......  
    10.         { },  
    11. };  

    注册spi_master


    在linux设备模型看来,代表SPI控制器的是第一节所定义的platform_device结构,但是对于SPI通用接口层来说,代表控制器的是spi_master结构,关于spi_master结构的描述,请参看第二篇文章:Linux SPI总线和设备驱动架构之二:SPI通用接口层。我们知道,设备和驱动匹配上后,驱动的probe回调函数就会被调用,而probe回调函数正是对驱动程序和设备进行初始化的合适时机,本例中,对应的probe回调是:s3c64xx_spi_probe:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static int s3c64xx_spi_probe(struct platform_device *pdev)  
    2. {  
    3.         ......  
    4.   
    5.         /* 分配一个spi_master结构 */  
    6.         master = spi_alloc_master(&pdev->dev,  
    7.                                 sizeof(struct s3c64xx_spi_driver_data));  
    8.         ......  
    9.   
    10.         platform_set_drvdata(pdev, master);  
    11.         ......  
    12.         master->dev.of_node = pdev->dev.of_node;  
    13.         master->bus_num = sdd->port_id;  
    14.         master->setup = s3c64xx_spi_setup;  
    15.         master->cleanup = s3c64xx_spi_cleanup;  
    16.         master->prepare_transfer_hardware = s3c64xx_spi_prepare_transfer;  
    17.         master->transfer_one_message = s3c64xx_spi_transfer_one_message;  
    18.         master->unprepare_transfer_hardware = s3c64xx_spi_unprepare_transfer;  
    19.         master->num_chipselect = sci->num_cs;  
    20.         master->dma_alignment = 8;  
    21.         master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(16) |  
    22.                                         SPI_BPW_MASK(8);  
    23.         /* the spi->mode bits understood by this driver: */  
    24.         master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;  
    25.         master->auto_runtime_pm = true;  
    26.   
    27.         ......  
    28.         /* 向通用接口层注册spi_master结构 */  
    29.         if (spi_register_master(master)) {  
    30.                 dev_err(&pdev->dev, "cannot register SPI master ");  
    31.                 ret = -EBUSY;  
    32.                 goto err3;  
    33.         }  
    34.   
    35.         ......  
    36. }  

    上述函数,除了完成必要的硬件资源初始化工作以外,最重要的工作就是通过spi_alloc_master函数分配了一个spi_master结构,初始化该结构,最终通过spi_register_master函数完成了对控制器的注册工作。从代码中我们也可以看出,spi_master结构中的几个重要的回调函数已经被赋值,这几个回调函数由通用接口层在合适的时机被调用,以便完成控制器和设备之间的数据交换工作。

    实现spi_master结构的回调函数


    事实上,SPI控制器驱动程序的主要工作,就是要实现spi_master结构中的几个回调函数,其它的工作逻辑,均由通用接口层帮我们完成,通用接口层会在适当的时机调用这几个回调函数,这里我只是介绍一下各个回调函数的作用,具体的实现例子,请各位自行阅读代码树中各个平台的例子(代码位于:/drivers/spi/)。

    int (*setup)(struct spi_device *spi)

    当协议驱动希望修改控制器的工作模式或参数时,会调用通用接口层提供的API:spi_setup(),该API函数最后会调用setup回调函数来完成设置工作。

    int (*transfer)(struct spi_device *spi, struct spi_message *mesg)

    目前已经可以不用我们自己实现该回调函数,初始化时直接设为NULL即可,目前的通用接口层已经实现了消息队列化,注册spi_master时,通用接口层会提供实现好的通用函数。现在只有一些老的驱动还在使用该回调方式,新的驱动应该停止使用该回调函数,而是应该使用队列化的transfer_one_message回调。需要注意的是,我们只能选择其中一种方式,设置了transfer_one_message回调,就不能设置transfer回调,反之亦然。

    void (*cleanup)(struct spi_device *spi)

    当一个SPI从设备(spi_device结构)被释放时,该回调函数会被调用,以便释放该从设备所占用的硬件资源。

    int (*prepare_transfer_hardware)(struct spi_master *master)

    int (*unprepare_transfer_hardware)(struct spi_master *master)

    这两个回调函数用于在发起一个数据传送过程前和后,给控制器驱动一个机会,申请或释放某些必要的硬件资源,例如DMA资源和内存资源等等。

    int (*prepare_message)(struct spi_master *master, struct spi_message *message)

    int (*unprepare_message)(struct spi_master *master, struct spi_message *message)

    这两个回调函数也是用于在发起一个数据传送过程前和后,给控制器驱动一个机会,对message进行必要的预处理或后处理,比如根据message需要交换数据的从设备,设定控制器的正确工作时钟、字长和工作模式等。

    int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg)

    当通用接口层发现master的队列中有消息需要传送时,会调用该回调函数,所以该函数是真正完成一个消息传送的工作函数,当传送工作完成时,应该调用spi_finalize_current_message函数,以便通知通用接口层,发起队列中的下一个消息的传送工作。
  • 相关阅读:
    【YBTOJ】【Luogu P4398】[JSOI2008]Blue Mary的战役地图
    【YBTOJ】【Luogu P2601】[ZJOI2009]对称的正方形
    Hystrix超时设置无效及解决原因
    nginx的压缩
    使用nginx配置本地yum 源
    通过nginx制作类似阿里云镜像下载的网站
    nginx下配置显示网站图标
    nginx---自定义日志格式和json日志
    git clone error: RPC failed; result=35, HTTP code = 0 fatal: The remote end hung up unexpectedly
    nginx---缓存
  • 原文地址:https://www.cnblogs.com/FarmPick/p/5422437.html
Copyright © 2011-2022 走看看