Freescale imx6 linux platform
驅動模型分析一.
概述
platform設備和驅動與linux設備模型密切相關。platform在linux設備模型中,其實就是一種虛擬總線沒有對應的硬件結構。它的主要作用就是管理系統的外設資源,比如io內存,中斷信號線。現在大多數處理器芯片都是soc,如Freescale
imx6,它包括處理器內核和系統的外設(lvds接口,nandflash接口等)。linux在引入了platform機制之後,內核假設所有的這些外設都掛載在platform虛擬總線上,以便進行統一管理。
二. platform
總線
1. 在系統中platform對應的文件drivers/base/platform.c,它不是作為一個模塊注冊到內核的,關鍵的注冊總線的函數由系統初始化部分,對應/init/main.c中的do_basic_setup函數間接調用。這裡可以看出platform非常重要,要在系統其他驅動加載之前注冊。下面分析platform總線注冊函數
[cpp] view
plain copy
1.
int __init platform_bus_init(
void)
2.
{
3.
int error;
4.
early_platform_cleanup();
5. error = device_register(&platform_bus);
6.
//總線也是設備,所以也要進行設備的注冊
7.
if (error)
8.
return error;
9. error = bus_register(&platform_bus_type);
10.
//注冊platform_bus_type總線到內核
11.
if (error)
12.
device_unregister(&platform_bus);
13.
return error;
14.
}
這個函數向內核注冊了一種總線。他首先由/drivers/base/init.c中的driver_init函數調用,driver_init函數由/init/main.c中的do_basic_setup函數調用,do_basic_setup這個函數由kernel_init調用,所以platform總線是在內核初始化的時候就注冊進了內核。
2. platform_bus_type 總線結構與設備結構
(1) platform總線設備結構
[cpp] view
plain copy
1.
struct device platform_bus = {
2.
.init_name = "platform",
3. };
platform總線也是一種設備,這裡初始化一個device結構,設備名稱platform,因為沒有指定父設備,所以注冊後將會在/sys/device/下出現platform目錄。
(2) platform總線總線結構
[cpp] view
plain copy
1.
struct bus_type platform_bus_type = {
2.
.name = "platform",
3. .dev_attrs = platform_dev_attrs,
4.
.match = platform_match,
5. .uevent = platform_uevent,
6.
.pm = &platform_dev_pm_ops,
7. };
platform_dev_attrs 設備屬性
platform_match match函數,這個函數在當屬於platform的設備或者驅動注冊到內核時就會調用,完成設備與驅動的匹配工作。
platform_uevent 熱插拔操作函數
三. platform
設備
1. platform_device 結構
[cpp] view
plain copy
1.
struct platform_device {
2.
const char * name;
3.
int id;
4.
struct device dev;
5. u32 num_resources;
6.
struct resource * resource;
7.
struct platform_device_id *id_entry;
8.
/* arch specific additions */
9.
struct pdev_archdata archdata;
10.
};
(1)platform_device結構體中有一個struct
resource結構,是設備占用系統的資源,定義在ioport.h中,如下
[cpp] view
plain copy
1.
struct resource {
2.
resource_size_t start;
3. resource_size_t end;
4.
const char *name;
5. unsigned
long flags;
6.
struct resource *parent, *sibling, *child;
7. };
(2) num_resources
占用系統資源的數目,一般設備都占用兩種資源,io內存和中斷信號線。這個為兩種資源的總和。
2. 設備注冊函數 imx_add_platform_device_dmamask
[cpp] view
plain copy
1. struct platform_device *__initimx_add_platform_device_dmamask(
2. constchar *name, int id,
3. conststruct resource *res, unsigned int num_resources,
4. constvoid *data, size_t size_data, u64 dmamask)
5. {
6. int ret= -ENOMEM;
7. structplatform_device *pdev;
8.
9. pdev =platform_device_alloc(name, id);
10. if(!pdev)
11. gotoerr;
12.
13. if(dmamask) {
14. /*
15. * This memory isn't freed when the device isput,
16. * I don't have a nice idea for thatthough. Conceptually
17. * dma_mask in struct device should not be apointer.
18. * Seehttp://thread.gmane.org/gmane.linux.kernel.pci/9081
19. */
20. pdev->dev.dma_mask=
21. kmalloc(sizeof(*pdev->dev.dma_mask),GFP_KERNEL);
22. if(!pdev->dev.dma_mask)
23. /*ret is still -ENOMEM; */
24. gotoerr;
25.
26. *pdev->dev.dma_mask= dmamask;
27. pdev->dev.coherent_dma_mask= dmamask;
28. }
29.
30. if(res) {
31. ret= platform_device_add_resources(pdev, res, num_resources);
32. if(ret)
33. gotoerr;
34. }
35.
36. if(data) {
37. ret= platform_device_add_data(pdev, data, size_data);
38. if(ret)
39. gotoerr;
40. }
41.
42. ret =platform_device_add(pdev);
43. if(ret) {
44. err:
45. if(dmamask)
46. kfree(pdev->dev.dma_mask);
47. platform_device_put(pdev);
48. returnERR_PTR(ret);
49. }
50.
51. returnpdev;
52. }
這個函數首先初始化了platform_device的device結構,然後調用platform_device_add,這個是注冊函數的關鍵,下面分析platform_device_add:
[cpp] view
plain copy
53.
int platform_device_add(
struct platform_device *pdev)
54.
{
55.
int i, ret = 0;
56.
57.
if (!pdev)
58.
return -EINVAL;
59.
60.
if (!pdev->dev.parent)
61. pdev->dev.parent = &platform_bus;
62.
//可以看出,platform設備的父設備一般都是platform_bus,所以注冊後的platform設備都出現在/sys/devices/platform_bus下
63. pdev->dev.bus = &platform_bus_type;
64.
//掛到platform總線上
65.
if (pdev->id != -1)
66.
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
67.
else 68.
dev_set_name(&pdev->dev, "%s", pdev->name);
69. //設置設備名字,這個名字與/sys/devices/platform_bus下的名字對應
70.
for (i = 0; i < pdev->num_resources; i++) { //下面操作設備所占用的系統資源
71.
struct resource *p, *r = &pdev->resource[i];
72.
73.
if (r->name == NULL)
74.
r->name = dev_name(&pdev->dev);
75.
76.
p = r->parent;
77.
if (!p) {
78.
if (resource_type(r) == IORESOURCE_MEM)
79. p = &iomem_resource;
80.
else if (resource_type(r) == IORESOURCE_IO)
81. p = &ioport_resource;
82.
}
83.
84.
if (p && insert_resource(p, r)) {
85. printk(KERN_ERR
86.
"%s: failed to claim resource %d\n",
87. dev_name(&pdev->dev), i);
88.
ret = -EBUSY;
89.
goto failed;
90.
}
91. }
92.
//上面主要是遍歷設備所占用的資源,找到對應的父資源,如果沒有定義,那麼根據資源的類型,分別賦予iomem_resource和ioport_resource,然後調用insert_resource插入資源。
93. //這樣系統的資源就形成了一個樹形的數據結構,便於系統的管理
94.
pr_debug("Registering platform device '%s'. Parent at %s\n",
95. dev_name(&pdev->dev), dev_name(pdev->dev.parent));
96.
97. ret = device_add(&pdev->dev);
98.
//注冊到設備模型中
99.
if (ret == 0)
100.
return ret;
101. failed:
102.
while (--i >= 0) {
103.
struct resource *r = &pdev->resource[i];
104.
unsigned
long type = resource_type(r);
105.
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
106.
release_resource(r);
107. }
108.
return ret;
109. }
3. imx6內核注冊platform設備過程
因為一種soc確定之後,其外設模塊就已經確定了,所以注冊platform設備就由板級初始化代碼來完成,在mx6中是board-mx6q_sabreauto..c的mx6_board_init函數中調用imx_add_platform_device_dmamask來完成注冊。如lvds,其是先調用imx6q_add_ldb(&ldb_data),然後調用imx_add_platform_device("mxc_ldb",-1,
res, ARRAY_SIZE(res),pdata, sizeof(*pdata)),然後調用imx_add_platform_device_dmamask,最後調用platform_device_add(pdev)。這個函數完成imx6的相關platform設備的注冊:
(1) imx_add_platform_device_dmamask函數是platform_device_add的簡單封裝,它向內核注冊一組platform設備
(2)下面以lvds為例說明,其他的類似。
platform_device在imx6中沒有直接定義,其只是定義了resource的相關內容,具體形式如下:
struct resource res[] = {
{
.start =data->iobase,
.end =data->iobase + data->iosize - 1,
.flags =IORESOURCE_MEM,
},
};
const struct imx_ldb_data imx6q_ldb_data __initconst =
imx_ldb_data_entry_single(MX6Q,SZ_4K);
#define imx_ldb_data_entry_single(soc, size) \
{ \
.iobase = soc ##_IOMUXC_BASE_ADDR, \
.iosize = size, \
}
可以看出,它占用的資源resource,定義如下:
[cpp] view
plain copy
struct resource res[] = {
{
.start = MX6Q_IOMUXC_BASE_ADDR,
.end = MX6Q_IOMUXC_BASE_ADDR+ SZ_4K - 1,
.flags =IORESOURCE_MEM,
},
};
這有一個元素,其資源類型是IORESOURCE_MEM代表io內存,起使地址MX6Q_IOMUXC_BASE_ADDR,這個是lvds寄存器的地址。
四. platform設備驅動
如果要將所寫的驅動程序注冊成platform驅動,那麼所做的工作就是初始化一個platform_driver,然後調用platform_driver_register進行注冊,其源代碼在文件ldb.c中。
1. 基本數據機構platform_driver
[cpp] view
plain copy
1.
struct platform_driver {
2.
int (*probe)(
struct platform_device *);
3.
int (*remove)(
struct platform_device *);
4.
void (*shutdown)(
struct platform_device *);
5.
int (*suspend)(
struct platform_device *, pm_message_t state);
6.
int (*resume)(
struct platform_device *);
7.
struct device_driver driver;
8.
struct platform_device_id *id_table;
9. };
這是platform驅動基本的數據結構,在驅動程序中我們要做的就是聲明一個這樣的結構並初始化。下面是lvds驅動程序對它的初始化:
[cpp] view
plain copy
10. static struct platform_driver mxcldb_driver ={
11. .driver= {
12. .name = "mxc_ldb",
13. },
14. .probe= ldb_probe,
15. .remove= ldb_remove,
16. .suspend= ldb_suspend,
17. .resume= ldb_resume,
18. };
上面幾個函數是我們要實現的,它將賦值給device_driver中的相關成員,probe函數是用來查詢特定設備是夠真正存在的函數。當設備從系統刪除的時候調用remove函數。
2. 注冊函數platform_driver_register
[cpp] view
plain copy
1.
int platform_driver_register(
struct platform_driver *drv)
2.
{
3. drv->driver.bus = &platform_bus_type;
4.
if (drv->probe)
5. drv->driver.probe = platform_drv_probe;
6.
if (drv->remove)
7. drv->driver.remove = platform_drv_remove;
8.
if (drv->shutdown)
9. drv->driver.shutdown = platform_drv_shutdown;
10.
return driver_register(&drv->driver);
11. }
這個函數首先使驅動屬於platform_bus_type總線,將platform_driver結構中的定義的probe,remove,shutdown賦值給device_driver結構中的相應成員,以供linux設備模型核心調用,然後調用driver_regster將設備驅動注冊到linux設備模型核心中。
五.
各環節的整合
前面提到imx6板級初始化程序將它所有的platform設備注冊到了linux設備模型核心中,在/sys/devices/platform目錄中都有相應的目錄表示。platform驅動則是由各個驅動程序模塊分別注冊到系統中的。但是他們是如何聯系起來的呢,這就跟linux設備模型核心有關系了。這裡簡要說明一下platform實現的方法。每當注冊一個platform驅動的時候就會調用driver_register,這個函數的調用會遍歷設備驅動所屬總線上的所有設備,並對每個設備調用總線的match函數。platform驅動是屬於platform_bus_type總線,所以調用platform_match函數。這個函數實現如下:
[cpp] view
plain copy
1.
static int platform_match(
struct device *dev,
struct device_driver *drv)
2.
{
3.
struct platform_device *pdev = to_platform_device(dev);
4.
struct platform_driver *pdrv = to_platform_driver(drv);
5.
6.
/* match against the id table first */
7.
if (pdrv->id_table)
8.
return platform_match_id(pdrv->id_table, pdev) != NULL;
9. /* fall-back to driver name match */
10.
return (strcmp(pdev->name, drv->name) == 0);
11. }
這個函數將device結構轉換為platform_devcie結構,將device_driver結構轉換為platform_driver結構,並調用platform_match_id對設備與驅動相關信息進行比較。如果沒有比較成功會返回0,以便進行下一個設備的比較,如果比較成功就會返回1,並且將device結構中的driver指針指向這個驅動,本例中driver
name為mxc_ldb,而device name為mxc_ldb,兩個相同,故匹配成功。然後調用device_driver中的probe函數,在lvds驅動中就是ldb_probe。這個函數是我們要編寫的函數。這個函數檢測驅動的狀態,並且測試能否真正驅動設備,並且做一些初始化工作。