基於platform匯流排的驅動分析
阿新 • • 發佈:2019-01-01
1.platform匯流排基本概念
- 裝置和驅動若基於裝置驅動模型,則它們通常都需要掛接在一種總線上。匯流排相對於裝置和驅動,可謂是“媒人”擔當
- 對於本身依附於 USB、 I2C、SPI 等的裝置而言,這自然不是問題。但是很多的裝置(比如led)實際並不依附於匯流排,沒了“媒人”,裝置和驅動怎麼產生聯絡呢?於是核心為這些可憐的裝置發明了一種虛擬的匯流排——platform(平臺匯流排)
- 掛接在platform上的裝置和驅動,就稱之為platform_device,和platform_driver
2.platform匯流排驅動工作流程
- 提供並註冊platform_device/裝置節點
- 提供並註冊platform_driver
- 當platform匯流排內的mach函式會不停的匹配driver和device(老核心是根據driver內的id、name元素;新核心是根據of_match_table中的compatible)
- 一旦匹配成功,則呼叫driver的probe(探測)函式開始正式執行驅動程式碼
3.platform匯流排驅動的獨立性和適應性
一個platform匯流排驅動程式可以對應多個裝置,並且裝置的變化也不會影響驅動。這是如何實現的呢?
- 簡單的說,這是一種類似傳參的機制。裝置將底層資訊(比如暫存器資訊、使用到的中斷號、裝置名稱等)傳遞給驅動,驅動本身程式碼不用變,只需要根據引數操作底層,便可適應裝置的變化
- 現代驅動設計理念就是演算法和資料分離,驅動原始碼中不攜帶資料,只負責演算法(對硬體的操作方法),這樣最大程度保持驅動的獨立性和適應性
- 具體的實現方法是:老核心中,platform_device包含了一個device結構體,其內部有一個 void *platform_data; 這個有點類似於給使用者提供的自留地,使用者可以在裡面存放各種底層資訊。當driver的probe(探測)函式執行時,platform_device會作為引數傳進去,這樣驅動就能夠間接的得到這個 void *platform_data,從而據此操作硬體;新核心則直接在裝置節點屬性中存放資料,驅動通過API讀取節點裡的資料
4.老核心下platform匯流排驅動的編寫方法
下面,以led驅動為例項,分析怎麼使用platform來寫驅動
- 根據上圖的流程,首先應該進入mach-xxx.c完成platform裝置的註冊。
- 第一步:建立適用於我們裝置的platform_data型別(為自留地設計一種格式)
- 第二步:為一個具體裝置例項化一個platform_data,用來存放該類裝置的底層資訊
- 第三步:建立一個具體platform裝置(例項化一個platform_device),並把各種資訊和platform_data填充入該裝置
- 第四步:把platform裝置丟到專門存放platform_device的陣列中,開機時系統會註冊陣列中所有裝置
- 先來看看mach-xxx.c中的情況,如果我們要寫新的platform_device,要注意mach-xxx.c內有沒有重複功能的。在該mach-xxx.c搜尋“platform”,尋找專門存放platform_device的陣列,發現裡面並沒有led,看來我們要自己從頭開始寫了
/*sjh_add*/
/*第一步:建立一個適用於我們裝置的platform_data型別*/
struct s5pv210_led_platdata {
unsigned int gpio;
unsigned int flags;
char *name;
char *def_trigger;
};
/*第二步:為一個具體裝置例項化一個platform_data*/
static struct s5pv210_led_platdata x210_led1_pdata = {
.gpio = S5PV210_GPJ0(3),
.flags = NULL,
.name = "led1",
.def_trigger = NULL,
};
/*第三步:例項化一個platform_device,正式建立裝置*/
static struct platform_device x210_led1 = {
.name = "s5pv210_led",//要和platform驅動中的名字對應
.id = 1,
.dev = {
.platform_data = &x210_led1_pdata,//底層資訊
},
};
/* 第四步:把我們的platform_device新增進陣列,開機時系統會註冊陣列中所有裝置*/
static struct platform_device *smdkc110_devices[] __initdata = {
/*sjh_add*/
&x210_led1,
#ifdef CONFIG_FIQ_DEBUGGER
&s5pv210_device_fiqdbg_uart2,
#endif
...
/*該陣列很長,後面就不貼了*/
- 然後我們還可以嘗試新增多個裝置,讓一個驅動對應多個led,我們只需重複第二、第三步即可。值得注意的是,驅動和裝置的匹配是靠name元素的,所以務必保證這幾個裝置的name元素都要和驅動的name元素相同,並且id不同就行了(如果name和id都一樣……那就分不清了……)
- 接著開始驅動部分的編寫
/*自留地格式,提供給probe和release函式,讓它們可以解析platdata*/
extern struct s5pv210_led_platdata {
unsigned int gpio;
unsigned int flags;
char *name;
char *def_trigger;
};
static int s5pv210_led_probe(struct platform_device *pdev)
{
/*匯入自留地格式s5pv210_led_platdata,這樣我們才能解析引數*/
struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
int ret = -1;
/*這是一個例子,我們如何通過傳入的引數獲得led的gpio編號資訊*/
gpio_num = pdata->gpio;
/*各種註冊、初始化操作*/
...
return 0;
}
/*解除安裝模組將觸發remove函式*/
static int s5pv210_led_remove(struct platform_device *pdev)
{
struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
/*各種登出操作*/
return 0;
}
/*定義我們的platform_driver。注意name要和platform_device中相同*/
static struct platform_driver s5pv210_led_driver = {
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
.name = "s5pv210_led",
.owner = THIS_MODULE,
},
};
/*模組與解除安裝載入函式,在裡面分別新增platform驅動的註冊和解除安裝函式*/
static int __init s5pv210_led_init(void)
{
return platform_driver_register(&s5pv210_led_driver);
}
static void __exit s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
//模組描述資訊
MODULE_LICENSE("GPL");
MODULE_AUTHOR("taurenking");
MODULE_DESCRIPTION("S5PV210 LED driver");
MODULE_ALIAS("S5PV210 LED driver");
- 驅動方面要注意的地方是傳參的問題,對於probe函式,傳進來的是指向platform_device型別例項的指標,而我們需要的底層資訊在platform_device中的dev內的platform_data中,前面的那幅圖可以很好的表明這個結構
- 為了解析這個引數,還需要extern一個s5pv210_led_platdata到本檔案,它是我們led裝置的platform_data型別(因為這個型別是我們自己寫在mach-xxx.c中的,沒有定義在標頭檔案中,故編譯器找不到),讓編譯器知道格式,才能夠解析
- 在解析引數前,先讓匯入自留地的格式
struct s5pv210_led_platdata *pdata = pdev->dev.platform_data;
,這樣編譯器才能知道格式,我們才能解析引數 - 那麼其他硬體操作函式怎麼獲得底層資訊呢?它們又沒有platform_data這種引數,我們其實在檔案頭部定義幾個全域性變數就能解決,在probe函式種解析底層資訊,然後賦值給全域性變數,比如前面程式碼中的
gpio_num = pdata->gpio;
,硬體操作函式呼叫全域性變數即可獲得裝置的底層資訊,例如
static void s5pv210_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (value == 0) {
gpio_set_value(gpio_num, 1);
}else{
gpio_set_value(gpio_num, 0);
}
}
5.老核心下廠商風格的platform匯流排驅動
有時,我們需要去分析一些由廠商實現的platform匯流排驅動,比如framebuffer。此類platform匯流排驅動本質上和我們之前分析的完全相同,只是在細節上略有區別罷了
- 以framebuffer為例,在mach-xxx中,我們發現在專門存放platform_device的陣列中,有一個和fb有關的platform裝置
s3c_device_fb
,它被定義在arch/arm/plat-xxx/devs.c內:
struct platform_device s3c_device_fb = {
.name = "s3cfb",
.id = -1,
.num_resources = ARRAY_SIZE(s3cfb_resource),
.resource = s3cfb_resource,
.dev = {
.dma_mask = &fb_dma_mask,
.coherent_dma_mask = 0xffffffffUL
}
};
- 不難發現,裡面並沒有填充platform_data。這意味著該裝置沒有platform_data嗎?顯然不是的,就在s3c_device_fb的下面,定義了一個函式
s3cfb_set_platdata
,該函式明顯是用來為platform裝置填充platform_data的 - 看一下該函式的reference,發現在mach-xxx裡的smdkc110_machine_init函式中被呼叫了,並且傳進去一個貌似是LCD硬體資訊的引數:
搜尋ek070tn93_fb_data,發現這個結構體內果然包括了LCD所有的硬體資訊。看來,這個結構體就是platform_data的靈魂
static struct s3c_platform_fb ek070tn93_fb_data __initdata = {
.hw_ver = 0x62,
.nr_wins = 5,
.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
.swap = FB_SWAP_WORD | FB_SWAP_HWORD,
.lcd = &ek070tn93,
.cfg_gpio = ek070tn93_cfg_gpio,
.backlight_on = ek070tn93_backlight_on,
.backlight_onoff = ek070tn93_backlight_off,
.reset_lcd = ek070tn93_reset_lcd,
};
- 說到底,廠商搞的這一出,其實就是繞了幾個圈子,把platform裝置和platform_data分開定義了,本質和我們之前的那種直接填充的方法沒有什麼不同
6.新核心下platform匯流排驅動的編寫方法
- 對於驅動本身來說,新核心下的變化倒不大;主要是platform裝置不再需要在mach-xxx中註冊,而是直接以節點形式定義在裝置樹中。platform裝置可以直接定義在dts的根節點內,比如imx6dl-hummingboard.dts內的ir-receiver裝置
/ {
model = "SolidRun HummingBoard DL/Solo";
compatible = "solidrun,hummingboard", "fsl,imx6dl";
ir_recv: ir-receiver {
compatible = "gpio-ir-receiver";
gpios = <&gpio1 2 1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hummingboard_gpio1_2>;
};
/*後面一堆程式碼就省略了*/
- 驅動程式將直接和裝置樹裡的裝置節點進行配對,是通過裝置節點中的compatible(相容性)來與裝置節點進行配對的。具體方法是定義一個of_match_table,只要裡面的compatible與裝置節點裡的compatible相同,那麼就觸發probe函式
/*驅動中定義的of_match_table*/
static struct of_device_id gpio_ir_recv_of_match[] = {
{ .compatible = "gpio-ir-receiver", },
{ },
};
/*of_match_table被繫結到driver結構體內*/
static struct platform_driver gpio_ir_recv_driver = {
.probe = gpio_ir_recv_probe,
.remove = gpio_ir_recv_remove,
.driver = {
.name = GPIO_IR_DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gpio_ir_recv_of_match),
},
};