通过第一篇文章,我们已经知道,整个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 plaincopy
  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. struct platform_device s3c64xx_device_spi0 = {
  8. .name       = "s3c6410-spi",
  9. .id     = 0,
  10. .num_resources  = ARRAY_SIZE(s3c64xx_spi0_resource),
  11. .resource   = s3c64xx_spi0_resource,
  12. .dev = {
  13. .dma_mask       = &samsung_device_dma_mask,
  14. .coherent_dma_mask  = DMA_BIT_MASK(32),
  15. },
  16. };

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

[cpp] view plaincopy
  1. static struct platform_device *crag6410_devices[] __initdata = {
  2. ......
  3. &s3c64xx_device_spi0,
  4. ......
  5. };
  6. static void __init xxxx_machine_init(void)
  7. {
  8. s3c64xx_spi0_set_platdata(NULL, 0, 2);
  9. //注册平台设备
  10. platform_add_devices(crag6410_devices, ARRAY_SIZE(crag6410_devices));
  11. }

s3c64xx_spi0_set_platdata函数的定义如下:

[cpp] view plaincopy
  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 plaincopy
  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. static int __init s3c64xx_spi_init(void)
  13. {
  14. return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
  15. }
  16. subsys_initcall(s3c64xx_spi_init);

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

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

上述函数,除了完成必要的硬件资源初始化工作以外,最重要的工作就是通过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函数,以便通知通用接口层,发起队列中的下一个消息的传送工作。

Linux SPI总线和设备驱动架构之三:SPI控制器驱动相关推荐

  1. Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化

    我们知道,SPI数据传输可以有两种方式:同步方式和异步方式.所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返 ...

  2. Linux SPI总线和设备驱动架构之一:系统概述

    SPI是"Serial Peripheral Interface" 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器.传感器.存储设备,SPI设备分为主设备和从设备两种,用 ...

  3. PHY驱动调试之 --- PHY控制器驱动(二)

    1. 前言 内核版本:linux 4.9.225,以freescale为例. 2. 概述 PHY芯片为OSI的最底层-物理层(Physical Layer),通过MII/GMII/RMII/SGMII ...

  4. Linux SPI总线和设备驱动架构之二:SPI通用接口层

    通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...

  5. linux 驱动没有设备id,linux不同总线的设备和驱动的匹配过程分析

    摘自: 前几日读书会,谈到linux中driver和device的匹配问题,我认为是通过设备名来匹配的,因为我之前看过platform的驱动,它就是通过设备name和驱动name来进行匹配,所以我确信 ...

  6. clip许可证设备id与绑定设备不匹配_LINUX SPI通用字符设备模型介绍(SPI模块分析结束篇)...

    在前面几篇文章我们分析了SPI驱动框架模型.SPI总线.SPI MASTER.SPI DEVICE.SPI通信接口等内容,本篇是SPI总线的最后一篇,主要介绍SPI通用字符设备. spi通用字符设备说 ...

  7. i2c驱动架构 davinc dm368 i2c驱动分析

    预备知识 在阅读本文最好先熟悉一种i2c设备的驱动程序,并且浏览一下i2c-core.c以及芯片提供商的提供的i2c总线驱动(i2c-davinci.c).标题党请见谅! 其实i2c接口非常的简单,即 ...

  8. Linux SPI总线和设备驱动架构

    http://blog.csdn.net/droidphone/article/details/24353293

  9. USB驱动程序设计之三USB鼠标驱动程序设计

    文章目录 USB驱动程序简介 USB驱动程序描述 USB设备列表 注册驱动 Linux USB 描述符 设备描述 设备描述 **配置描述:** 接口描述 接口设置 接口描述符 端点描述 USB URB ...

最新文章

  1. python进行矩阵计算公式_纯python进行矩阵的相乘运算的方法示例
  2. eclipse访问别人 mysql数据库_eclipse访问mysql数据库!
  3. Python 生成requirement 使用requirements.txt
  4. 史上最全Git学习教程
  5. C++_类和对象_C++继承_继承的方式_公共继承_保护继承_私有继承---C++语言工作笔记062
  6. 不租服务器,自建个人商业网站(如何购买域名)
  7. ios平台实现视频H264硬编码及软编码(附完整demo)
  8. Topcoder EllysBottles 题解
  9. 王之泰201771010131《面向对象程序设计(java)》第十二周学习总结
  10. aseprite手机版_Voxel像素画体素模型转拼豆图纸方法
  11. Pycharm ssh远程调试报错: cannot connect to X server的解决方法
  12. 2019全网最全大数据系统学习路径
  13. POJ3322Bloxorz I
  14. php扒皮,扒皮b站美妆up主兰花
  15. 企业如何借助制造业ERP系统,做好生产排产管理?
  16. 离散数学笔记_第一章:逻辑和证明(1)
  17. 如何批量图片识别文字?两个好用的电脑端软件教程
  18. 大学生魅力讲话实操题库
  19. 联想笔记本电量显示为0
  20. 中达优控触摸屏宏指令DELAY报错

热门文章

  1. GUI应用程序架构的十年变迁:MVC,MVP,MVVM,Unidirectional,Clean
  2. 前端JS——滑动滑块验证登录(源码及效果)
  3. Java 相对路径和绝对路径的简单介绍
  4. 专家建议:维护边缘网络安全的五项原则
  5. 2019~2020年IDC行业发展趋势预测
  6. 专家揭示模块化数据中心的真谛
  7. random java怎么用_java中Random使用步骤是怎样的?具体怎么做?
  8. AI:2020年7月10日世界人工智能大会WAIC青少年人工智能创新发展论坛《人工智能从娃娃抓起》
  9. Dataset之RentListingInquries:RentListingInquries(Kaggle竞赛)数据集的简介、下载、案例应用之详细攻略
  10. Keras之ML~P:基于Keras中建立的回归预测的神经网络模型(根据200个数据样本预测新的5+1个样本)——回归预测