1、概述:
通常在Linux中,把SoC系統中集成的獨立外設單元(如:I2C、IIS、RTC、看門狗等)都被當作平臺設備來處理。
從Linux2.6起,引入了一套新的驅動管理和註冊機制:Platform_device和Platform_driver,來管理相應設備。
Linux中大部分的設備驅動,都可以使用這套機制,設備用platform_device表示,驅動用platform_driver進行註冊。
Linux platform driver機制和傳統的device_driver機制相比,一個十分明顯的優勢在於platform機制將本身的資源註冊進內核,由內核統一管理,在驅動程序中使用這些資源時通過platform_device提供的標準接口進行申請並使用。
這樣提高了驅動和資源管理的獨立性,並且擁有較好的可移植性和安全性。對在每個掛在虛擬的platform bus的設備作__driver_attach() ->driver_probe_device()
開始真正的探測,如果probe成功,則綁定設備到該驅動。
定義 platform_device
struct platform_device
{
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
每個具體的驅動都對應一個這樣的結構體。
(注意,這個名字一定要和後面platform_driver.driver->name相同,因爲在註冊具體的設備驅動時會遍歷這個結構體查找相應的數據結構,後面會詳細講解)
struct resource
{
resource_size_t start; //定義資源的起始地址
resource_size_t end; //定義資源的結束地址
const char *name; //定義資源的名稱
unsigned long flags; //定義資源的類型,比如MEM,IO,IRQ,DMA類型
struct resource *parent, *sibling, *child; //資源鏈表指針
};
主要用於定義具體設備佔用的硬件資源(如:地址空間、中斷號等;
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();
}
此函數中調用了platform_add_devices() -> platform_device_register()註冊platform設備
註冊順序根據同文件夾下的
{
&s3c_device_ohci,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_iis,
&s3c_device_dm9k,
&s3c24xx_uda134x,
&s3c_device_sdi,
};
結構體進行
這些設備的初始化一般都在arch/arm/plat-s3c24xx/devs.c下
我們以s3c_device_wdt爲例進行觀察:
/* Watchdog */
//看門狗資源結構體
static struct resource s3c_wdt_resource[] = {
[0] = {
.start = S3C24XX_PA_WATCHDOG,
.end = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
.flags = IORESOURCE_MEM, //看門狗所使用的IO口範圍
},
[1] = {
.start = IRQ_WDT,
.end = IRQ_WDT,
.flags = IORESOURCE_IRQ, //看門狗所使用的中斷資源
}
};
//定義了一個看門狗結構體
struct platform_device s3c_device_wdt = {
.name = "s3c2410-wdt", //驅動名稱
.id = -1, //id號,-1代表自動分配
.num_resources = ARRAY_SIZE(s3c_wdt_resource),
//指定資源數量
.resource = s3c_wdt_resource, //指定資源結構體
};
platform_driver在具體的硬件設備驅動編寫中完成:
同plartform_device相似,需要定義並實現以下結構體
{
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 device_driver driver;
};
其中除了一些函數指針外,還有一個一般驅動的device_driver結構。
/*Watchdog平臺驅動結構體,平臺驅動結構體定義在platform_device.h中,該結構體內的接口函數需要單獨實現*/
static struct platform_driver watchdog_driver =
{
.probe = watchdog_probe, /*Watchdog探測函數*/
.remove = __devexit_p(watchdog_remove),/*Watchdog移除函數*/
.shutdown = watchdog_shutdown, /*Watchdog關閉函數*/
.suspend = watchdog_suspend, /*Watchdog掛起函數*/
.resume = watchdog_resume, /*Watchdog恢復函數*/
.driver =
{
/*注意這裏的名稱一定要和系統中定義平臺設備的地方一致,這樣才能把平臺設備與該平臺設備的驅動關聯起來*/
.name = "s3c2410-wdt",
.owner = THIS_MODULE,
},
};
static int __init watchdog_init(void)
{
/*將Watchdog註冊成平臺設備驅動*/
return platform_driver_register(&watchdog_driver);
}
static void __exit watchdog_exit(void)
{
/*註銷Watchdog平臺設備驅動*/
platform_driver_unregister(&watchdog_driver);
}
module_init(watchdog_init);
module_exit(watchdog_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linux");
MODULE_DESCRIPTION("S3C2440 Watchdog Driver");
static int __devinit watchdog_probe(struct platform_device *pdev)
{
int ret;
int started = 0;
struct resource *res;/*定義一個資源,用來保存獲取的watchdog的IO資源*/
wdt_irqno = platform_get_irq(pdev, 0);
/*申請Watchdog中斷服務,這裏使用的是快速中斷:IRQF_DISABLED。中斷服務程序爲:wdt_irq,將Watchdog平臺設備pdev做參數傳遞過去了*/
ret = request_irq(wdt_irqno, wdt_irq, IRQF_DISABLED, pdev->name, pdev);
/*獲取watchdog平臺設備所使用的IO端口資源,注意這個IORESOURCE_MEM標誌和watchdog平臺設備定義中的一致*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*申請watchdog的IO端口資源所佔用的IO空間(要注意理解IO空間和內存空間的區別),request_mem_region定義在ioport.h中*/
wdt_mem = request_mem_region(res->start, res->end - res->start + 1, pdev->name);
/*將watchdog的IO端口占用的這段IO空間映射到內存的虛擬地址,ioremap定義在io.h中。
注意:IO空間要映射後才能使用,以後對虛擬地址的操作就是對IO空間的操作,*/
wdt_base = ioremap(res->start, res->end - res->start + 1);
return ret;
}
/*Watchdog平臺驅動的設備移除接口函數的實現*/
static int __devexit wdt_remove(struct platform_device *dev)
{
/*釋放獲取的Watchdog平臺設備的IO資源*/
release_resource(wdt_mem);
kfree(wdt_mem);
wdt_mem = NULL;
/*同watchdog_probe中中斷的申請相對應,在那裏申請中斷,這裏就釋放中斷*/
free_irq(wdt_irqno, dev);
wdt_irq = NULL;
/*釋放獲取的Watchdog平臺設備的時鐘*/
clk_disable(wdt_clock);
clk_put(wdt_clock);
wdt_clock = NULL;
/*釋放Watchdog設備虛擬地址映射空間*/
iounmap(wdt_base);
return 0;
}
#ifdef CONFIG_PM
/*定義兩個變量來分別保存掛起時的WTCON和WTDAT值,到恢復的時候使用*/
static unsigned long wtcon_save;
static unsigned long wtdat_save;
/*Watchdog平臺驅動的設備掛起接口函數的實現*/
static int wdt_suspend(struct platform_device *dev, pm_message_t state)
{
/*保存掛起時的WTCON和WTDAT值*/
wtcon_save = readl(wdt_base + S3C2410_WTCON);
wtdat_save = readl(wdt_base + S3C2410_WTDAT);
/*停止看門狗定時器*/
wdt_start_or_stop(0);
return 0;
}
/*Watchdog平臺驅動的設備恢復接口函數的實現*/
static int wdt_resume(struct platform_device *dev)
{
/*恢復掛起時的WTCON和WTDAT值,注意這個順序*/
writel(wtdat_save, wdt_base + S3C2410_WTDAT);
writel(wtdat_save, wdt_base + S3C2410_WTCNT);
writel(wtcon_save, wdt_base + S3C2410_WTCON);
return 0;
}
#else /*配置內核時沒選上電源管理,Watchdog平臺驅動的設備掛起和恢復功能均無效,這兩個函數也就無需實現了*/
#define wdt_suspend NULL
#define wdt_resume NULL
#endif