Linux下的spi驱动

一、概述

 首先我们对SPI做个认识,SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。这4根线分别是它们是数据输入SDI(MISO)、数据输出SDO(MOSI)、时钟SCLK、片选CS。

  SPI常用四种数据传输模式,主要是由输出串行同步时钟极性(CPOL)和相位(CPHA)进行配置。如果CPOL= 0,串行同步时钟的空闲状态为低电平;如果CPOL= 1,串行同步时钟的空闲状态为高电平。如果CPHA= 0,在串行同步时钟的前沿(上升或下降)数据被采样;如果CPHA = 1,在串行同步时钟的后沿(上升或下降)数据被采样。下面是针对4中模式的时序图:

 

注:我们需要设置的模式是根据spi芯片手册上支持的模式来设置的。 

二、linux下SPI驱动开发

  在带有linux系统的平台中和纯单片机中对于spi驱动的开发是有着本质的区别的:在linux系统中spi驱动是作为一个子系统模块的,更通俗的讲就是一套通信协议(spi驱动也叫协议驱动),也就是说无论你选择的平台是什么,只要是用的linux内核,那么都使用的是该协议,内核提供给用户的接口函数都是一致的,驱动的实现也是一致的,是用户不需要更改的,而用户需要关心的是:   一、你要打开的spi设备节点是哪个(spidevx.x,这个spi设备节点是在spi子系统中创建的。

  二、你根据平台来选择配置多少个spi控制器以及添加多少个spi设备(这些信息都是在平台依赖的板级信息里面配置)。  

  每个平台中从上层spi控制器到下面的spi设备都是有一定的层次结构的,可以先通过下面的图概要的理解一下,后面还会从代码的来详细的分析一下其实现原理。

                                     

 

我们以上面的这个图为思路

1、 Platform bus

Platform bus对应的结构是platform_bus_type,这个内核开始就定义好的。对于itop4412这个平台,platform bus的定义和注册可以参看arch/arm/driver/base/platform.c文件

点击(此处)折叠或打开

  1. struct bus_type platform_bus_type = {                  
  2.                         .name = "platform",                   //定义platform总线
  3.                         .dev_attrs= platform_dev_attrs,
  4.                         .match= platform_match,
  5.                         .uevent= platform_uevent,
  6.                         .pm= &platform_dev_pm_ops,
  7. };

  8. int __init platform_bus_init(void)
  9. {
  10.     int error;
  11. early_platform_cleanup();

  12.     error = device_register(&platform_bus);
  13.     if (error)
  14.         return error;

  15.     error =  bus_register(&platform_bus_type);  //注册platform总线
  16.     if (error)
  17.         device_unregister(&platform_bus);
  18.     return error;
  19. }

2、Platform_device

SPI控制器对应platform_device的定义方式,同样以itop4412中的SPI控制器为例,参看arch/arm/mach-exynos/dev-spi.c文件

点击(此处)折叠或打开

  1. struct platform_device exynos_device_spi0 = {
  2.                 .name = "s3c64xx-spi",                              //名称,要和Platform_driver匹配
  3.                 .id = 0,                                            //第0个控制器,itop4412中有3个控制器
  4.                 .num_resources = ARRAY_SIZE(exynos_spi0_resource),  //占用资源的种类
  5.                 .resource = exynos_spi0_resource,                   //指向资源结构数组的指针
  6.                 .dev = {
  7.                         .dma_mask = &spi_dmamask, //dma寻址范围 
  8.                         .coherent_dma_mask = DMA_BIT_MASK(32),      //可以通过关闭cache等措施保证一致性的dma寻址范围
  9.                         .platform_data = &exynos_spi0_pdata,        //特殊的平台数据,参看后文
  10.                  },
  11. };

  12. static struct s3c64xx_spi_info exynos_spi0_pdata = {
  13.                 .cfg_gpio = s5pc1xx_spi_cfg_gpio,                   //用于控制器管脚的IO配置
  14.                 .fifo_lvl_mask = 0x7f,
  15.                 .rx_lvl_offset = 15,
  16.                 .high_speed = 1,
  17.                 .clk_from_cmu = true,
  18.                 .tx_st_done = 25,
    };

  19. static int exynos_spi_cfg_gpio(struct platform_device *pdev){
  20.          int gpio; 

  21.          switch (pdev->id) {
  22.              case 0:
  23.                      s3c_gpio_cfgpin(EXYNOS4_GPB(0), S3C_GPIO_SFN(2));
  24.                      s3c_gpio_cfgpin(EXYNOS4_GPB(2), S3C_GPIO_SFN(2));
  25.                      s3c_gpio_cfgpin(EXYNOS4_GPB(3), S3C_GPIO_SFN(2));
  26.                      s3c_gpio_setpull(EXYNOS4_GPB(0), S3C_GPIO_PULL_UP);
  27.                      s3c_gpio_setpull(EXYNOS4_GPB(2), S3C_GPIO_PULL_UP);
  28.                      s3c_gpio_setpull(EXYNOS4_GPB(3), S3C_GPIO_PULL_UP);

  29.                      for (gpio = EXYNOS4_GPB(0); gpio < EXYNOS4_GPB(4); gpio++)
  30.                          s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
  31.                   
  32.              break;
  33.              case 1:
  34.                      s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_SFN(2));
  35.                      s3c_gpio_cfgpin(EXYNOS4_GPB(6), S3C_GPIO_SFN(2));
  36.                      s3c_gpio_cfgpin(EXYNOS4_GPB(7), S3C_GPIO_SFN(2));
  37.                      s3c_gpio_setpull(EXYNOS4_GPB(4), S3C_GPIO_PULL_UP);
  38.                      s3c_gpio_setpull(EXYNOS4_GPB(6), S3C_GPIO_PULL_UP);
  39.                      s3c_gpio_setpull(EXYNOS4_GPB(7), S3C_GPIO_PULL_UP);

  40.                      for (gpio = EXYNOS4_GPB(4); gpio < EXYNOS4_GPB(8); gpio++)
  41.                          s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
  42.               break;
  43.               case 2:
  44.                      s3c_gpio_cfgpin(EXYNOS4_GPC1(1), S3C_GPIO_SFN(5));
  45.                      s3c_gpio_cfgpin(EXYNOS4_GPC1(3), S3C_GPIO_SFN(5));
  46.                      s3c_gpio_cfgpin(EXYNOS4_GPC1(4), S3C_GPIO_SFN(5));
  47.                      s3c_gpio_setpull(EXYNOS4_GPC1(1), S3C_GPIO_PULL_UP);
  48.                      s3c_gpio_setpull(EXYNOS4_GPC1(3), S3C_GPIO_PULL_UP);
  49.                      s3c_gpio_setpull(EXYNOS4_GPC1(4), S3C_GPIO_PULL_UP);

  50.                      for (gpio = EXYNOS4_GPC1(1); gpio < EXYNOS4_GPC1(5); gpio++)
  51.                          s5p_gpio_set_drvstr(gpio, S5P_GPIO_DRVSTR_LV3);
  52.                break;
  53.                default:
  54.                     dev_err(&pdev->dev, "Invalid SPI Controller number!");
  55.              
  56.                return -EINVAL;
  57. }

3、Platform_driver

再看platform_driver,参看drivers/spi/spi_s3c64xx.c文件

点击(此处)折叠或打开

  1. static struct platform_driver s3c64xx_spi_driver = {
  2.                           .driver = {
  3.                                 .name = "s3c64xx-spi",                 //名称,和platform_device对应
  4.                                 .owner = THIS_MODULE,
  5.                            },
  6.                           .remove  = s3c64xx_spi_remove,
  7.                           .suspend = s3c64xx_spi_suspend,
  8.                           .resume  = s3c64xx_spi_resume,
  9.                 };

  10. platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe)//注册s3c64xx_spi_driver

  和平台中注册的platform_device匹配后,调用s3c64xx_spi_probe。然后根据传入的platform_device参数,构建一个用于描述SPI控制器的结构体spi_master,并注册。spi_register_master(master)。后续注册的spi_device需要选定自己的spi_master,并利用spi_master提供的传输功能传输spi数据。

  和I2C类似,SPI也有一个描述控制器的对象叫spi_master。其主要成员是主机控制器的序号(系统中可能存在多个SPI主机控制器)、片选数量、SPI模式和时钟设置用到的函数、数据传输用到的函数等。

点击(此处)折叠或打开

  1. struct spi_master {
  2.            struct device dev;
  3.            s16 bus_num;                                //表示是SPI主机控制器的编号。由平台代码决定
  4.            u16 num_chipselect;                         //控制器支持的片选数量,即能支持多少个spi设备
  5.            int (*setup)(struct spi_device *spi);       //针对设备设置SPI的工作时钟及数据传输模式等。在spi_add_device函数中调用。
  6.            int (*transfer)(struct spi_device *spi,struct spi_message *mesg); //实现数据的双向传输,可能会睡眠
  7.            void (*cleanup)(struct spi_device *spi);    //注销时调用
  8.         };

4、Spi bus

Spi总线对应的总线类型为spi_bus_type,在内核的drivers/spi/spi.c中定义

点击(此处)折叠或打开

  1. struct bus_type spi_bus_type = {
  2.                           .name = "spi",
  3.                           .dev_attrs = spi_dev_attrs,
  4.                           .match = spi_match_device,
  5.                           .uevent = spi_uevent,
  6.                           .suspend = spi_suspend,
  7.                           .resume = spi_resume,
  8.                 };

点击(此处)折叠或打开

  1. static int spi_match_device(struct device *dev, struct device_driver *drv)
  2.                   {
  3.                           const struct spi_device *spi = to_spi_device(dev);
  4.                           return strcmp(spi->modalias, drv->name) == 0;
  5.                  }

5、spi_device

下面该讲到spi_device的构建与注册了。spi_device对应的含义是挂接在spi总线上的一个设备,所以描述它的时候应该明确它自身的设备特性、传输要求、及挂接在哪个总线上。参看arch/arm/mach-exynos/mach-itop4412.c文件。 

点击(此处)折叠或打开

  1. static struct spi_board_info spi2_board_info[] __initdata = {
  2.                           {
  3.                                 .modalias = "spidev", //sp设备的名字,这个名字将会和spidev.c中的驱动名字匹配。
  4.                                 .platform_data = NULL;
  5.                                 .max_speed_hz = 10*1000*1000,   //最大的spi时钟频率
  6.                                 .bus_num = 2,                   //设备连接在spi控制器0上
  7.                                 .chip_select = 0,               //片选线号
  8.                                 .mode = SPI_MODE_0, //CPOL=0, CPHA=0 此处选择具体数据传输模式
  9.                                 .controller_data = &spi2_csi[0],
  10.                           },
  11. };

  12. static struct s3c64xx_spi_csinfo spi2_csi[] = {
  13.                     [0] = {
  14.                                 .line = EXYNOS4_GPC1(2),
  15.                                 .set_level = gpio_set_value,
  16.                                 .fb_delay = 0x2,
  17.                           },
  18. };

  19. spi_register_board_info(spi2_board_info, ARRAY_SIZE(spi2_board_info));
  20. //注册spi_board_info。这个代码会把spi_board_info注册要链表board_list上。

事实上上文提到的spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用spi_match_master_to_boardinfo(有些平台是scan_boardinfo函数)来对比传入的总线号和板级配置的总线号是否匹配,然后创建并注册spi_device。

点击(此处)折叠或打开

  1. static void spi_match_master_to_boardinfo(struct spi_master *master,struct spi_board_info *bi)
  2. {
  3.     struct spi_device *dev;

  4.     if (master->bus_num != bi->bus_num)   //spi控制器的总线号和板级配置的总线是否匹配
  5.         return;

  6.     dev = spi_new_device(master, bi);    //创建spi新设备
  7.     if (!dev)
  8.         dev_err(master->dev.parent, "can't create new device for %s\n",bi->modalias);
  9. }

6、spi_driver

本文先以linux内核中的/driver/spi/spidev.c驱动为参考。

点击(此处)折叠或打开

  1. static struct spi_driver spidev_spi_driver = { //spi_driver的构建
  2.                 .driver = {
  3.                                   .name = "spidev"
  4.                                   .owner = THIS_MODULE,
  5.                           },
  6.                 .probe = spidev_probe,
  7.                 .remove = __devexit_p(spidev_remove),
  8. };

  9. spi_register_driver(&spidev_spi_driver);//spi driver的注册

  10. 在有匹配的spi device时,会调用m25p_probe

  11. static int __devinit spidev_probe(struct spi_device *spi)
  12.                   {
  13.                   ……
  14.         }

根据传入的spi_device参数,可以找到对应的spi_master。接下来就可以利用spi子系统为我们完成数据交互了。

发布了31 篇原创文章 · 获赞 35 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/bingjia103126/article/details/52241264
今日推荐