加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

手把手教你中斷喚醒系統(tǒng)

2021/12/28
1661
閱讀需 9 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

在消費類電子中,功耗是很重要的,甚至項目后期一直在調(diào)功耗,看看哪里還可以再省電。由此就有了 Linux 電源管理子系統(tǒng),該子系統(tǒng)包含很多方面:什么時候可以降幀、什么時候可以關(guān)掉其他 CPU core、系統(tǒng)運行時如果某外設很少用需要讓它運行時休眠、系統(tǒng)休眠時要保證哪些外設可以喚醒系統(tǒng)。

博主今天要討論的,就是一個按鍵如何喚醒系統(tǒng),類似于手機的電源鍵。

這個功能并不是新功能,所以 Linux 內(nèi)部有一個 demo 可以使用,先教大家如何使用該 demo,然后較大家如何撰寫中斷喚醒系統(tǒng)驅(qū)動。

官方 demo

demo 目錄:/kernel4.14/drivers/input/keyboard/gpio_keys.c

該驅(qū)動是專門為按鍵準備的,是一個身經(jīng)百戰(zhàn)的驅(qū)動,任何時候測試按鍵中斷或者中斷喚醒系統(tǒng)都可以用它,很多時候比自己寫的驅(qū)動靠譜。

要想使用該驅(qū)動,首先在該目錄的 Makefile 中增加:

obj-y  += gpio_keys.o

設備樹中增加:

gpio-keys {
  compatible = "gpio-keys";
  #address-cells = <1>;
  #size-cells = <0>;
  autorepeat;
  key0 {
   label = "GPIO Key Enter";
   linux,code = <KEY_ENTER>;
   gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
   gpio-key,wakeup;
  };
};

compatible 屬性是 “gpio-keys”,gpio_keys.c 文件的674行會匹配這個屬性,匹配到了該驅(qū)動就會運行。

linux,code 屬性是按鍵值,Linux 對所有按鍵事件都有編號,所以KEY_ENTER 實際是一個數(shù)字,是驅(qū)動向上層報告的一個按鍵值。

gpios 屬性是標明哪一個 GPIO 口,低電平觸發(fā),大家可以自己選一個 GPIO。

gpio-key,wakeup 是代表此GPIO支持中斷喚醒,你也可以寫成:wakeup-source。新老版本而已。

修改就是這么簡單,不過語法要符合各位手中的開發(fā)板平臺。然后編譯出內(nèi)核和設備樹文件,下載到板子中。(Linux 內(nèi)核根目錄會有 .config 文件,確保 CONFIG_PM_SLEEP=y 有打開)

如果驅(qū)動加載成功,在 /proc/interrupts 中可以看到:

 

從左往右第一列是軟件中斷號(唯一)。

第二列是 CPU,表示該中斷在該CPU上觸發(fā)了多少次,多核會有多列。

第三列是中斷控制器,imx6ull開發(fā)板根中斷控制器是GPC,外部中斷控制器是gpio-mxc,兩者是級聯(lián)關(guān)系。

第四列是硬件中斷號,也就是GPIO口編號。

第五列表示該中斷是邊沿觸發(fā)還是電平觸發(fā)。

第六列是中斷名稱,可以找到一個 GPIO Key Enter,如果驅(qū)動加載成功就能看到,如果失敗就看不到。

驗證方法

在內(nèi)核中,休眠方式有很多種,可以通過下面命令查看

# cat /sys/power/state

常用的休眠方式有freeze、standby、mem、disk

freeze:凍結(jié)I/O設備,將它們置于低功耗狀態(tài),使處理器進入空閑狀態(tài),喚醒最快,耗電比其它standby, mem, disk方式高

standby:除了凍結(jié)I/O設備外,還會暫停系統(tǒng),喚醒較快,耗電比其它 mem, disk方式高

mem:將運行狀態(tài)數(shù)據(jù)存到內(nèi)存,并關(guān)閉外設,進入等待模式,喚醒較慢,耗電比disk方式高

disk:將運行狀態(tài)數(shù)據(jù)存到硬盤,然后關(guān)機,喚醒最慢

 

示例:

 # echo mem > /sys/power/state

系統(tǒng)進入睡眠后,基本都會停掉UI、停掉串口,串口無法操作,如圖:

按下按鍵,系統(tǒng)恢復:

當然這里的 log 并不完整,輸入 dmesg 可以看到完整 log:

PM:power manager

具體干了什么,圖中有解釋,分為 suspend 過程和 resume 過程。

其實一個中斷讓它支持喚醒系統(tǒng),最主要是多了兩個函數(shù):suspend、resume。

suspend 函數(shù)在系統(tǒng)整體 suspend 的時候,會調(diào)用每個外設注冊的 suspend,我們在這個函數(shù)中調(diào)用 enable_irq_wake,表示該中斷在系統(tǒng)休眠時是 enable 狀態(tài)。

resume 函數(shù)在系統(tǒng)整體 resume 的時候,會調(diào)用每個外設注冊的 resume 函數(shù),在 resume 函數(shù)中調(diào)用 disable_irq_wake ,表示該中斷在系統(tǒng)運行時不需要。兩者成對使用。

具體參看下面文章,寫的很好:

http://www.wowotech.net/irq_subsystem/irq_handle_procedure.html

大家也可以研究一下 gpio_keys.c,該驅(qū)動看起來比較復雜,但是很完善,畢竟身經(jīng)百戰(zhàn),什么因素都考慮到了,測試就用它!

博主寫的 demo

博主下面給的是簡化版,并且自測OK,分享給大家,以后如果需要可以copy

xxx.c

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <linux/cdev.h>

static int gpionum = 0;
static int irqnum = 0;

static irqreturn_t my_handler(int irq, void *dev_id)
{
 printk("%srn",__FUNCTION__);
 return IRQ_HANDLED;
}

static int gpio_keys_probe(struct platform_device *pdev)
{
 int ret = 0;
 struct device_node *node = NULL;; /* 設備節(jié)點*/
 
 node = of_find_compatible_node(NULL,NULL,"atkalpha-key");
 if (node == NULL){
  printk("%s:atkalpha-key node not find!rn",__FUNCTION__);
  return -EINVAL;
 }
 
 /* 提取 GPIO */
 gpionum = of_get_named_gpio(node,"key-gpio", 0);
 if (gpionum < 0) {
   printk("of_get_named_gpio can't get keyrn");
 }
 
 /* 初始化 key 所使用的 IO,并且設置成中斷模式 */
 gpio_request(gpionum, "key-gpio");
 gpio_direction_input(gpionum); 
 
 irqnum = gpio_to_irq(gpionum);
 
 ret = request_irq(irqnum,my_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "my-key", NULL);
 if(ret < 0){
  printk("irq %d request failed!rn", irqnum);
  return -EFAULT;
 }
 return 0;
}


static const struct of_device_id gpio_keys_of_match[] = {
 { .compatible = "atkalpha-key", },
 { },
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);

static int gpio_keys_remove(struct platform_device *pdev)
{
 return 0;
}

static int gpio_keys_suspend(struct device *dev)
{
 printk("%srn",__FUNCTION__);
 enable_irq_wake(irqnum);
 return 0;
}

static int gpio_keys_resume(struct device *dev)
{
 printk("%srn",__FUNCTION__);
 disable_irq_wake(irqnum);
 return 0;
}

static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend, gpio_keys_resume);

static struct platform_driver gpio_keys_device_driver = {
 .probe  = gpio_keys_probe,
 .remove  = gpio_keys_remove,
 .driver  = {
  .name = "my-key",
  .pm = &gpio_keys_pm_ops,
  .of_match_table = of_match_ptr(gpio_keys_of_match),
 }
};

static int __init gpio_keys_init(void)
{
 return platform_driver_register(&gpio_keys_device_driver);
}

static void __exit gpio_keys_exit(void)
{
 platform_driver_unregister(&gpio_keys_device_driver);
}

module_init(gpio_keys_init);
module_exit(gpio_keys_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jason");
MODULE_DESCRIPTION("Keyboard driver for GPIOs");
MODULE_ALIAS("platform:gpio-keys");

xxx.dts

key {
  #address-cells = <1>;
  #size-cells = <1>;
  compatible = "atkalpha-key";
  key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
  interrupt-parent = <&gpio1>;
  interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
  gpio-key,wakeup;
  status = "okay";
};

最后再總結(jié)一下:中斷喚醒系統(tǒng)和普通的驅(qū)動區(qū)別在于,多了兩個函數(shù):suspend 和 resume,在 suspend 函數(shù)中,調(diào)用 enable_irq_wake,表示該中斷號在系統(tǒng)休眠時也是 enable 狀態(tài),可以觸發(fā)中斷。在 resume 函數(shù)中,調(diào)用 disable_irq_wake ,恢復原始的中斷觸發(fā)路徑。

然后使用 SIMPLE_DEV_PM_OPS 宏將 suspend 和 resume 函數(shù)注冊到 gpio_keys_pm_ops 操作集,最終由 platform 注冊到系統(tǒng)中。這樣完成后,系統(tǒng)休眠過程中就會調(diào)用到設備注冊的 suspend,系統(tǒng)喚醒過程中就會調(diào)用設備注冊的 resume 函數(shù)。

至于 probe 函數(shù)的書寫,我在 GPIO 子系統(tǒng)和中斷子系統(tǒng)系列文章都講過這些函數(shù)的使用,大家可以去我的網(wǎng)站查看:

http://www.linuxer.vip

note:該 demo 只用來喚醒系統(tǒng),如果你的中斷是在 I2C 等設備驅(qū)動中,喚醒系統(tǒng)后要立刻在中斷處理函數(shù)中進行 I2C 通信,寫法不太一樣,但是框架相同。

另外,該驅(qū)動的中斷處理函數(shù)中沒做什么東西,因此喚醒后執(zhí)行完中斷處理函數(shù)后又會睡過去。如果你想要該中斷喚醒系統(tǒng)后讓系統(tǒng)一直處于喚醒狀態(tài),請在中斷處理函數(shù)中使用 __pm_stay_awake() 和 __pm_relax()函數(shù)。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

研究生在讀,熟悉硬件、STM32單片機、嵌入式Linux。已收獲小米、聯(lián)發(fā)科、浙江大華、上能電氣、英威騰、匯川技術(shù)、格力、富士康等大廠offer。在這里分享求職經(jīng)驗、嵌入式學習規(guī)劃、考研、嵌入式Linux技術(shù)文章等。