Linux下的platform總線驅動(一)

Linux下的platform總線驅動(一)

原創 2012年12月01日 09:22:43

版權所有,轉載請說明轉自 http://my.csdn.net/weiqing1981127
 

原創作者:南京郵電大學  通信與信息系統專業 研二 魏清

一.Platform設備驅動概念

主要講解平臺設備驅動的模型和基本概念,同時因爲驅動加載的方式有動態加載和靜態加載兩種方式,這裏我們分別對動態加載和靜態加載兩種情況下,如何使用平臺設備和驅動加以敘述。最後使用mini2440開發板,運用Platformdevice_attribute機制,編寫按鍵驅動代碼和測試代碼。

 

我們知道linux內核中常見的的總線有I2C總線,PCI總線,串口總線,SPI總線,PCI總線,CAN總線,單總線等,所以有些設備和驅動就可以掛在這些總線上,然後通過總線上的match進行設備和驅動的匹配。但是有的設備並不屬於這些常見總線,所以我們引入了一種虛擬總線,也就是platform總線的概念,對應的設備叫做platform設備,對應的驅動叫做platform驅動。當然引入platform的概念,可以做的與板子相關的代碼和驅動的代碼分離,使得驅動有更好的可擴展性和跨平臺性。

 

1.Platform總線

struct bus_type platform_bus_type = {

       .name             = "platform",  //

       .dev_attrs       = platform_dev_attrs,  //屬性

       .match           = platform_match,  //設備和驅動的匹配函數

       .uevent           = platform_uevent,   //卸載處理

       .pm         = &platform_dev_pm_ops,  //電源管理

};

我們看看設備和驅動的匹配函數match

static int platform_match(struct device *dev, struct device_driver *drv)

{

       struct platform_device *pdev = to_platform_device(dev);   //獲得平臺設備

       struct platform_driver *pdrv = to_platform_driver(drv);    //獲得平臺驅動

       if (pdrv->id_table)           //如果平臺驅動有支持項,進入platform_match_id

              return platform_match_id(pdrv->id_table, pdev) != NULL;

       return (strcmp(pdev->name, drv->name) == 0);   //沒有支持項,則老實匹配名字

}

通過上面這個match函數我們知道,如果驅動中定義了驅動支持項,那麼在總線執行match函數中,就會將驅動支持項中每一個名字和設備名字匹配,看看是否匹配成功。如果驅動沒有設置支持項,就會把驅動的名字和設備的名字匹配,如果一樣,則匹配成功。

 

2.Platform設備

struct platform_device {

       const char      * name;  //

       int           id;  

       struct device   dev;      //內嵌設備

       u32         num_resources;   //資源個數

       struct resource       * resource;   //資源結構體

       struct platform_device_id      *id_entry;

       struct pdev_archdata     archdata;

};

我們重點來看看platform_device中資源結構體的定義

struct resource {

       resource_size_t start;  //起始地址

       resource_size_t end;  //結束地址

       const char *name;   //

       unsigned long flags;  //標號

       struct resource *parent, *sibling, *child; 

};

對於這個資源結構體中的flags標號可以有IORESOURCE_IOIORESOURCE_MEMIORESOURCE_IRQIORESOURCE_DMA四種選擇,重點是申請內存(IORESOURCE_MEM)和申請中斷號(IORESOURCE_IRQ)用的比較多。

 

2.1Platform設備的靜態加載

所謂的靜態加載,就是把platform設備編譯進內核,對於platform_device的定義常常在BSP中實現,我們這裏拿Mini2440舉例,看看對於的BSP文件mach-smdk2440.c

struct platform_device s3c_device_lcd = {

       .name               = "s3c2410-lcd",

       .id             = -1,

       .num_resources       = ARRAY_SIZE(s3c_lcd_resource),

       .resource   = s3c_lcd_resource,

       .dev              = {

              .dma_mask            = &s3c_device_lcd_dmamask,

              .coherent_dma_mask     = 0xffffffffUL

       }

};

這是基於Mini2440LCD平臺設備在BSP文件中的定義,那麼我們怎麼把它加入內核呢?

static struct platform_device *smdk2440_devices[] __initdata = {

       &s3c_device_usb,

       &s3c_device_lcd,  //添加LCD平臺設備

       &s3c_device_wdt,

       &s3c_device_i2c0,

       &s3c_device_iis,

};

嗯,原來我們建立了一個platform_device數組,然後把LCDplatform_device添加到這個數組中,那麼這個platform_device數組怎麼註冊到內核的呢?

static void __init smdk2440_machine_init(void)

{

       s3c24xx_fb_set_platdata(&smdk2440_fb_info);

       s3c_i2c0_set_platdata(NULL);

       platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));//加到內核

       smdk_machine_init();

}

看到了吧,在smdk2440_machine_init中,我們調用了platform_add_devices函數來把platform_device註冊到內核,再繼續跟蹤下platform_add_devices

int platform_add_devices(struct platform_device **devs, int num)

{

       int i, ret = 0;

       for (i = 0; i < num; i++) {

              ret = platform_device_register(devs[i]);

              if (ret) {

                     while (--i >= 0)

                            platform_device_unregister(devs[i]);  //註冊設備

                     break;

              }

       }

       return ret;

}

好了,到此爲止,我們已經看到了如果添加platform_device,以及這個platform_device又是如何被註冊到內核的全過程。

 

除了BSP中定義的資源外,有的設備可能還會有一些配置信息,而這些配置信息依賴於板子,不適合放到驅動中,爲此,我們的platform提供了平臺數據platform_data的支持。在內核中添加平臺數據有兩種方式,仍然以LCD爲例

static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {  //平臺數據

       .displays  = &smdk2440_lcd_cfg,

       .num_displays = 1,

       .default_display = 0,

       .lpcsel            = ((0xCE6) & ~7) | 1<<4,

};

上面的smdk2440_fb_info就是LCD的平臺數據,我們怎麼把這個LCD的平臺數據告訴LCDplatform_device呢?

static void __init smdk2440_machine_init(void)

{

       s3c24xx_fb_set_platdata(&smdk2440_fb_info);  //添加平臺數據

       s3c_i2c0_set_platdata(NULL);

       platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

       smdk_machine_init();

}

看到沒?上面的s3c24xx_fb_set_platdata函數就完成了平臺數據的添加,繼續跟蹤這個s3c24xx_fb_set_platdata函數

void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)

{

       struct s3c2410fb_mach_info *npd;

       npd = kmalloc(sizeof(*npd), GFP_KERNEL);

       if (npd) {

              memcpy(npd, pd, sizeof(*npd));

              s3c_device_lcd.dev.platform_data = npd;  //平臺數據添加的實現

       } else {

              printk(KERN_ERR "no memory for LCD platform data\n");

       }

}

好了,我們可以看到其實把這個平臺數據保存在了平臺設備中內嵌的設備結構體的platform_data中。剛纔說了添加平臺數據有兩種方式,根據上面的原理,其實我們可以直接把平臺數據保存在了平臺設備中內嵌的設備結構體的platform_data中,具體代碼如下

struct platform_device s3c_device_lcd = {

       .name               = "s3c2410-lcd",

       .id             = -1,

       .num_resources       = ARRAY_SIZE(s3c_lcd_resource),

       .resource   = s3c_lcd_resource,

       .dev              = {

              .dma_mask            = &s3c_device_lcd_dmamask,

              .coherent_dma_mask     = 0xffffffffUL

              .platform_data=&smdk2440_fb_info  //添加平臺數據

       }

};

到此爲止,我們已經明白了platform_device的靜態添加全過程。

 

2.2 Platform設備的動態加載

由於靜態添加platform_device需要最後編譯內核,這個不利於修改,所以在開發階段,我們可以採用platform設備的動態加載方法。具體操作是:先分配platform_device,然後向platform_device中添加資源結構體,最後把platform_device註冊到內核,對應三個函數如下

struct platform_device  my_device = platform_device_alloc("s3c2410-buttons", -1);

platform_device_add_resources(my_device, s3c_buttons_resource, 3);

ret =  platform_device_add(my_device);

當然,上面三個函數還是封裝在模塊加載函數中,也就是把平臺設備的加載寫成一個模塊的形式。

 

3. Platform驅動

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 (*resume)(struct platform_device *);            //恢復

       struct device_driver driver;        //內嵌設備驅動

       struct platform_device_id *id_table;  //驅動支持項

};

根據上面的platform_driver結構體的定義,我們需要思考下platform驅動名字在哪裏呢?實際上在內嵌的設備驅動中定義的。

 

3.1 Platform驅動的靜態加載

寫一個驅動,測試驅動階段我們一般採用動態加載的方式,當驅動已經成型,我們就會採用靜態加載的方式,把驅動編譯入內核。把驅動靜態編譯入內核的方式主要是根據MakefileKconfig兩張地圖,在Makefile中添加驅動文件名,在Kconfig中添加對應的驅動菜單選項,當我面make zImage時就會自動編譯我們的驅動文件。

 

3.2 Platform驅動的動態加載

拿一個基於平臺設備的按鍵驅動例子看看

static struct platform_driver my_driver = {

     .probe = my_probe,   //探測函數

     .remove = my_remove,

     .driver = {

          .owner = THIS_MODULE,

          .name = "s3c2410-buttons",

     },

};

上面是按鍵驅動中定義了的一個platform_driver,然後我們只需要在驅動模塊加載函數中執行platform_driver_register(&my_driver)就可以把platform驅動加入內核了。在我們進行insmod加載時就會調用這個模塊加載函數,從而註冊platform驅動。

 

二.平臺設備的資源

1.平臺數據和私有數據的區別

前面在講平臺設備的靜態加載的時候,我們提到平臺數據的概念,在內核驅動代碼中還會出現私有數據這一名詞。那麼平臺數據和私有數據有什麼區別呢?首先平臺數據是由於引入平臺設備而產生的,平臺數據主要保存的是一些依賴的板子的配置信息,平臺數據的定義是定義在平臺設備所在的BSP中的,我們在平臺驅動中可以進行讀取到在BSP中定義的平臺數據。而私有數據是作爲一個驅動中保存設備信息的一個結構體,它定義在平臺驅動中,而不是BSP中,我們在平臺驅動中可以把一個設備結構體設置爲這個平臺驅動的私有數據,也可以根據這個平臺設備,讀取這個平臺設備的私有數據。

 

好了,下面我們先看看怎麼在平臺驅動中讀取在BSP中定義的平臺數據,仍然以LCD爲例,只需要在設備驅動需要獲取平臺數據的地方執行如下代碼

struct s3c2410fb_mach_info *pdata=pdev->dev.platform_data;

 

接下來,我們研究下私有數據。私有數據的定義各種各樣,總之是一個結構體。那麼怎麼將一個設備結構體設置爲平臺設備的私有數據呢?

struct  buttons  *key

platform_set_drvdata(pdev, key)

同樣怎麼根據這個平臺設備,讀取這個平臺設備的私有數據呢?

Struct  buttons  *keyt=platform_get_drvdata(pdev)

 

最後補充兩個點:第一,根據經驗發現平臺數據是爲私有數據服務的,也就是平臺數據可能成爲私有數據的一部分。第二,對於由設備獲得平臺設備的情況,我們可以通過*pdev=to_platform_device(dev)代碼獲得。

 

2. Platform設備資源的讀取

我們在BSP中定義了平臺設備的資源,那麼怎麼獲取這些資源呢?首先我們要明白,設備和驅動的第一次匹配是發生在總線上的match函數中,這次匹配成功後所做的操作只是把設備和驅動相連。當我們執行平臺驅動中的probe時,會發生第二次設備和驅動的匹配,也就是所謂的探測。所以,我們對在BSP中定義的平臺設備的資源會在平臺驅動的probe函數中讀取到,下面我們就看看如何讀取這些資源了。

對於資源中的存儲空間的資源讀取,首先讀取資源,然後申請空間,最後完成由虛擬地址到物理地址的映射。具體函數如下

struct resource      *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

struct resource  *buttons_mem = request_mem_region(res->start,

                                    res->end-res->start+1, pdev->name);

void __iomem  *buttons_base = ioremap(res->start, res->end - res->start + 1);

對於中斷資源的讀取,只要一步如下操作即可。

struct resource  *buttons_irq1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章