wakeup_in休眠唤醒
問題背景:實現一個休眠喚醒的功能,并可控制的使單板進入休眠或者喚醒的狀態(管腳拉低進入休眠狀態,拉高恢復)。以此來達到LPM(低功耗模式)的目的。
文檔以wakeup_in管腳的休眠喚醒功能為例,描述了一個平臺驅動從注冊,匹配以及Probe內容的填充的一整個驅動實現的流程。
Wakeup_in管腳休眠喚醒功能的調試,大致可以分為下面這幾個步驟來實現:
目錄
1. 配置設備樹
2. 注冊驅動
3. probe函數
4. 驅動內容填充
5. 上層處理
1. 配置設備樹
Pinctrl.dtsi配置:
在驅動的注冊過程中,設備樹起的是信息傳遞的作用。所以不論是根據驅動找設備樹,還是根據設備樹找對應的驅動代碼,都是可以通過compatible這個屬性來查找。
?節點名稱 {
compatible:compatible屬性是一個字符串的列表。(用于驅動和設備的匹配綁定)。wakeup_in_gpio:指向gpio控制器tlmm_pinmux(引用該節點),gpiox_82引腳,低電平有效。qcom,gpio-names:goio名稱。
Pinctrl-names:定義了pin腳用到的state列表。
Pinctrl - x:pin腳的兩種配置狀態。(在驅動中獲取pin腳狀態時會根據名稱來匹配)。
};
GPIO的信息在/sys/kernel/debug/gpio文件內可以查看:
在驅動中獲取gpio號時,獲取到的不是82,而是一個與Linux通用GPIO API一起使用的GPIO編號,這里是924-1023,其中gpio82對應的GPIO編號是1006。
2. 注冊驅動
platfprm_driver結構體如下:
平臺驅動結構體內,內嵌繼承了一個設備驅動結構體?。后續的注冊過程,會完成這個結構體相關內容的填充。
平臺驅動的設計主要是完成平臺驅動結構體的填充和注冊。
?以wakeup_in驅動為例,完成了:
平臺驅動的probe函數實現:.probe = wakeup_in_probe,。它是驅動的入口函數,當總線完成了設備的 match?操作以后就會進入驅動中該函數的運行。
平臺驅動的remove函數實現:.remove= wakeup_in_remove,
實現設備驅動的name、owner、pm、of_match_table變量。
注冊的過程:
這里是實現對device_driver結構體內相關函數和變量的賦值。并將 driver的總線類型初始化為platform_bus_type。賦值完成后,將device_driver結構體作為參數傳遞,繼續注冊:
device_driver結構體如下:
?繼續跟代碼:
driver_find傳入兩個參數,name和bus。通過驅動的name在bus上查找這個驅動是否已經注冊過了,防止重復注冊。(未注冊則返回NULL)。
接下來的這個bus_add_driver接口,會根據總線類型把驅動添加到bus上,也是驅動正式開始注冊的入口:
先通過bus_get接口獲取總線(這里bus_type對應的是platform_bus_type)。
klist_init,kobject_init_and_add 和klist_add_tail,主要完成了Kobject的初始化和將初始化的kobject尾插到kset鏈表中。并給驅動創建目錄(sys/bus/xxx/driver/xxx)。
到這塊為止,其實已經將驅動注冊到了內核中。接下里的操作,就是嘗試給這個驅動匹配對應的設備。
driver_attach:嘗試將這個驅動和設備進行綁定。
這里會獲取設備鏈表里的每一個device,并調用fn(dev,data),也就是__driver_attach:
這里的match調用的是platform_match來進行匹配:
先將dev和drv轉換為平臺設備和驅動,然后會進行一個匹配方式的選擇:這里采用OF類型(設備樹類型)來進行匹配:
這里的drv->of_match_table,是在平臺驅動結構體填充時賦值的:
name/type/compatible這三個匹配項,只要有一個不為空,就滿足匹配條件,繼續往下執行。
of_device_id結構體:
然后這里會進行一個匹配內容的選擇,通過name/type/compatible。可以看出來,compatible是匹配優先級最高的項。這里使用compatible來進行匹配。
如果匹配成功了,則最后返回1。然后回到__driver_attach,執行device_driver_attach:
進入really_probe函數:將device結構中的driver指針指向這個驅動,這里就正式完成了驅動設備的匹配。
接下來會調用上面注冊的drv->probe,也就是平臺驅動結構體填充時賦值的probe。并且將與驅動匹配的dev作為參數傳給drv的probe函數。
至此,驅動的整個注冊匹配過程就結束了,接下來就開始probe該驅動。
3. probe函數
?定義一個要用到的結構體:
wakeup_in管腳的休眠喚醒,在probe中主要體現在對pin、gpio、irq等資源的申請和初始化,以及相關功能函數的初始化。
調用devm_pinctrl_get函數,向內核申請dev的pin腳資源。成功則會返回一個指向申請到的pin腳號的指針。(一個pinctrl的句柄,后面就可以通過該句柄,來對申請的pin腳進行操作)。
pinctrl_lookup_state這個函數,會根據傳入的第二個參數state_name去遍歷p->state鏈表的所有的狀態,通過name來進行匹配,如果匹配成功,則返回指向這個狀態的指針。
然后通過pinctrl_select_state這個函數,來將設備樹中的pinctrl中的信息配置到該pin中。
通過of_get_named_gpio函數,根據第二個參數去設備樹中查找對應的gpio,如果查詢成功,則返回一個與Linux通用GPIO API一起使用的GPIO編號。
像pin/gpio這些在內核中都屬于資源,所以使用之前先申請。通過pinctrl_gpio_request函數來向內核申請一個pin腳當作gpio來使用。
申請到gpio之后,通過pinctrl_gpio_direction_input函數,將gpio配置為輸入方向。
通過gpio_to_irq函數,來獲取gpio對應的中斷號,成功則返回該中斷號。
通過gpio_to_irq獲取到gpio對應的中斷號后,再調用request_irq函數進行中斷的注冊:將這個中斷號、中斷的irq_handler、中斷觸發方式以及中斷的名稱注冊到內核里。(注冊的中斷可以在/proc/interrupts文件內查看)。
通過wakeup_source_register函數來創建喚醒源設備,并將喚醒源設備加入全局的喚醒源設備列表,成功則返回指向這個喚醒源設備的指針。(注冊的喚醒源設備可以在/sys/kernel/debug/wakeup_sources文件內查看)。
調用create_workqueue函數來創建一個工作隊列,成功則返回指向該workqueue的指針。
調用INIT_WORK()函數來創建一個工作函數,并將該工作函數賦值給pdata->wk_work變量。
實現驅動LPM模式的suspend和resume,需要有一個中斷喚醒源來將系統從sleep狀態喚醒。通過調用enable_irq_wake(irq)函數,使該irq具有喚醒系統的功能?。(如果不設置該函數,則進休眠后就不會醒來)。
Probe主要的流程如圖所示:
?注:中斷注冊成功之后,要確保該中斷有喚醒系統的特性。調用enable_irq_wake()接口來實現該功能。?
Probe完成之后,接下來需要填充驅動所要實現的內容:在wakeup_in管腳拉低時,表示允許模組進入休眠狀態,拉高時喚醒模組。
Linux pm core提供了wakelock及autoslepp內核休眠機制。autosleep 和 wakelock是并行存在,只有 wakelock 喚醒鎖全部釋放且 autosleep 為 enable 時,系統才能進入休眠。
autosleep節點路徑為/sys/power/autosleep,該值為mem表示打開autoslepp功能,值為off表示關閉。
4. 驅動內容填充
外部觸發中斷(拉高拉低wakeup_in) --> irq_handler() --> timer_handler() --> work() -->odm_notify()。
拉高或者拉低wakeup_in管腳,觸發中斷,執行irq_handler:
在中斷處理函數中調用mod_timer()來重新啟動定時器。
定時時間超時后,執行timer_handle:
在定時器處理函數中,調用queue_work()來將pdata->work工作函數提交到pdata->wk_wq工作隊列的工作鏈表中,并喚醒等待隊列,然后執行該工作函數。
通過gpio_get_value()接口獲取當前wakeup_in管腳的電平狀態,來判斷是要進休眠還是要喚醒
如果是要進休眠,則調用_pm_relax()來給wakelock釋放鎖。
如果是要喚醒,則調用_pm_stay_awake()來給wakelock持鎖。這里傳入的參數都是probe中申請的指向喚醒源設備的指針。
串口發送awk '{ printf("%-32s %d\n", $1, $6) }' /sys/kernel/debug/wakeup_sources指令可以查看當前設備持鎖情況。
要使設備能進休眠或者喚醒,需要滿足兩個條件:持鎖&&autosleep寫mem;釋放鎖&&autosleep寫off。
work函數中完成了持鎖釋放鎖的操作,然后在進休眠時調用netlink_report_suspend,喚醒時調用netlink_report_resume函數:
休眠和喚醒對應不同的msg.id,然后調用odm_notify()函數,攜帶著對應的msg信息傳遞到上層去處理。
流程框圖如圖:
5. 上層處理
上層會根據傳遞上來的msg,根據不同的msg.id來執行對應的case:
如果是要進休眠,則調用frwk_system函數來給autosleep寫mem;如果是要喚醒,則給autosleep寫off。
至此,休眠和喚醒各自的兩個條件都滿足了,就可以實現休眠喚醒功能了。
總結
以上是生活随笔為你收集整理的wakeup_in休眠唤醒的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通达信l2数据接口具体有哪些功能可提供?
- 下一篇: 芯片丨美光最新财报电话会议透露了三个重要