1. 程式人生 > >platform_device與platform_driver && i2c驅動是如何probe的 && spi驅動是如何probe的

platform_device與platform_driver && i2c驅動是如何probe的 && spi驅動是如何probe的

      做Linux方面也有三個多月了,對程式碼中的有些結構一直不是很明白,比如platform_device與platform_driver一直分不清關係。在網上搜了下,做個總結。兩者的工作順序是先定義platform_device -> 註冊 platform_device->,再定義 platform_driver-> 註冊 platform_driver。

 (1)platform_device裝置的註冊過程必須在相應裝置驅動載入之前被呼叫,因為驅動註冊時需要匹配核心中所以已註冊的裝置名。platform_device 是在系統啟動時在init.c 裡的s3c_arch_init() 函式裡進行註冊的。這個函式申明為arch_initcall(s3c_arch_init); 會在系統初始化階段被呼叫。arch_initcall 的優先順序高於module_init,所以會在Platform 驅動註冊之前呼叫。現在核心中不是採用arch_initcall(s3c_arch_init) 註冊platform_device 結構體而是通過.init_machine成員將其儲存在arch_initcall(customize_machine)等待呼叫(在mach-smdk6410.c中定義的MACHINE_START到MACHINE_END);其實質是一樣的均放在.initcall3.init等待呼叫。之後再定義結構體struct platform_driver,在驅動初始化函式中呼叫函式platform_driver_register() 註冊 platform_driver。詳細過程描述如下:

      Linux從2.6版本開始引入了platform這個概念,在開發底層驅動程式時,首先要確認的就是裝置的資源資訊,在2.6核心中將每個裝置的資源用結構platform_device來描述,該結構體定義在kernel/include/linux/platform_device.h中,

struct platform_device

{
      const char * name;
      u32  id;
      struct device dev;
      u32  num_resources;
      struct resource * resource;
};


該結構一個重要的元素是resource,該元素存入了最為重要的裝置資源資訊,定義在kernel/include/linux/ioport.h中,
比如:

struct resource 

{
       const char *name;
       unsigned long start, end;
       unsigned long flags;
       struct resource *parent, *sibling, *child;
};

例項如:

static struct resource s3c_usb_resource[] = {
 [0] = {
       .start = S3C_PA_USBHOST,
       .end   = S3C_PA_USBHOST + S3C_SZ_USBHOST - 1,
       .flags = IORESOURCE_MEM,
     },
 [1] = {
       .start = IRQ_UHOST,
       .end   = IRQ_UHOST,
       .flags = IORESOURCE_IRQ,
     }
};


以上是6410的USB  HOST分配的資源資訊。第1組描述了這個usb host裝置所佔用的匯流排地址範圍,起始地址和大小由硬體決定,IORESOURCE_MEM表示第1組描述的是記憶體型別的資源資訊;第2組描述了這個usb host裝置的中斷號,也由硬體設定,IORESOURCE_IRQ表示第2組描述的是中斷資源資訊。裝置驅動會根據flags來獲取相應的資源資訊。

      有了resource資訊,就可以定義platform_device了:

struct platform_device s3c_device_usb = {
         .name    = "s3c2410-ohci",  //s3c6410-usb
         .id    = -1,
         .num_resources   = ARRAY_SIZE(s3c_usb_resource),
         .resource   = s3c_usb_resource,
         .dev              = {
                 .dma_mask = &s3c_device_usb_dmamask,
                 .coherent_dma_mask = 0xffffffffUL
             }
};


有了platform_device就可以呼叫函式platform_add_devices向系統中新增該裝置了。系統中的裝置資源都可以採用這種方式列舉在一起,然後成一個指標陣列,如:

static struct platform_device *smdk6410_devices[] __initdata = {

......

 &s3c_device_usbgadget,
 &s3c_device_usb,  //jeff add.

......

}

然後在6410的初始化函式smdk6410_machine_init()中執行:

platform_add_devices(smdk6410_devices, ARRAY_SIZE(smdk6410_devices));將所有的device新增進系統。platform_add_devices的好處在於它是一次性的執行多個platform_device_register。

(2) 至於驅動程式需要實現結構體struct platform_driver,也定義在kernel/include/linux/platform_device.h中:

struct platform_driver {
      int (*probe)(struct platform_device *);
      int (*remove)(struct platform_device *);
      void (*shutdown)(struct platform_device *);
      int (*suspend)(struct platform_device *, pm_message_t state);
      int (*suspend_late)(struct platform_device *, pm_message_t state);
      int (*resume_early)(struct platform_device *);
      int (*resume)(struct platform_device *);
      struct pm_ext_ops *pm;
      struct device_driver driver;
};


則該處的USB HOST實現是:

static struct platform_driver ohci_hcd_s3c2410_driver = {
     .probe  = ohci_hcd_s3c2410_drv_probe,
     .remove  = ohci_hcd_s3c2410_drv_remove,
     .shutdown = usb_hcd_platform_shutdown,
     /*.suspend = ohci_hcd_s3c2410_drv_suspend, */
     /*.resume = ohci_hcd_s3c2410_drv_resume, */
     .driver  = {
          .owner = THIS_MODULE,
          .name = "s3c2410-ohci",
        },
};


      在驅動初始化(ohci-hcd.c的1124行)函式中呼叫函式platform_driver_register()註冊該platform_driver,需要注意的是s3c_device_usb結構中name元素和ohci_hcd_s3c2410_driver 結構中driver.name必須是相同的,這樣在platform_driver_register()註冊時會對所有已註冊的platform_device中元素的name和當前註冊的platform_driver的driver.name進行比較,只有找到具備相同名稱的platform_device存在後,platform_driver才能註冊成功。當註冊成功時會呼叫platform_driver結構元素probe函式指標,這裡就是ohci_hcd_s3c2410_drv_probe開始探測載入。platform driver中的函式都是以platform device作為引數進入。

(3)為什麼兩個name的名字必須匹配才能實現device和driver的繫結?(1)在核心初始化時kernel_init()->do_basic_setup()->driver_init()->platform_bus_init()初始化platform_bus(虛擬匯流排);(2)設備註冊的時候platform_device_register()->platform_device_add()->(pdev->dev.bus = &platform_bus_type)把裝置掛在虛擬的platform bus下;(3)驅動註冊的時候platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev(),對每個掛在虛擬的platform bus的裝置作__driver_attach()->driver_probe_device(),判斷drv->bus->match()是否存在並且是否執行成功,此時通過指標執行platform_match,比較strncmp(pdev->name, drv->name, BUS_ID_SIZE),如果相符就呼叫really_probe(實際就是執行的相應裝置的platform_driver->probe(platform_device),注意platform_drv_probe的_dev引數是由bus_for_each_dev的next_device獲得)開始真正的探測載入,如果probe成功則繫結該裝置到該驅動。

      當進入probe函式後,需要獲取裝置的資源資訊,根據引數type所指定型別,例如IORESOURCE_MEM,來分別獲取指定的資源。
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);當然,也可以固定資源型別,如獲取資源中的中斷號:struct int platform_get_irq(struct platform_device *dev, unsigned int num);

      probe函式一般完成硬體裝置使能,struct resource的獲取以及虛擬地址的動態對映和具體型別裝置的註冊(因為平臺裝置只是一種虛擬的裝置型別);remove函式完成硬體裝置的關閉,struct resource以及虛擬地址的動態對映的釋放和具體型別裝置的登出。只要和核心本身執行依賴性不大的外圍裝置 ( 換句話說只要不在核心執行所需的一個最小系統之內的裝置 ), 相對獨立的擁有各自獨自的資源 (addresses and IRQs) ,都可以用platform_driver 實現。如:lcd,usb,uart 等,都可以用platfrom_driver 寫,而timer,irq等最小系統之內的裝置則最好不用platfrom_driver 機制,實際上核心實現也是這樣的。

====================i2c驅動是如何probe的==================

 (1)在新加I2c裝置驅動時,i2C的device和driver是如何掛載到一起的?即probe是如何開始的,也會有如上類似的關聯。比如struct i2c_device_id,struct i2c_driver,i2c_add_driver,i2c_register_board_info,詳情見 http://blog.csdn.net/lanmanck/article/details/7836734,簡單明瞭。

(2)xxxxx_probe(struct i2c_client *, const struct i2c_device_id *)函式中兩個形參又是哪裡來的呢?參見http://bbs.csdn.net/topics/350096754

(3)4.4的i2c驅動可否在安卓6.0的系統中使用?答案是可以的。4.4的I2C裝置多用i2c_register_board_info來註冊,但在6.0上多用dts來關聯。有種簡單的辦法把4.4的驅動如何在6.0上使用,在i2c_init中起始位置用i2c_register_board_info來註冊上即可。

====================spi驅動是如何probe的==================

       對於具體的某個裝置驅動,有spi_register_driver註冊struct spi_driver結構變數,這裡面有spi_driver的id_table的name,還有spi_driver的driver的name,他們會在spi_match_device中與modalias進行字串匹配。