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子系統爲我們完成數據交互了。

發佈了30 篇原創文章 · 獲贊 25 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章