手把手教你中断唤醒系统

系统 Linux
中断唤醒系统和普通的驱动区别在于,多了两个函数:suspend 和 resume,在 suspend 函数中,调用 enable_irq_wake,表示该中断号在系统休眠时也是 enable 状态,可以触发中断。

[[442463]]

在消费类电子中,功耗是很重要的,甚至项目后期一直在调功耗,看看哪里还可以再省电。由此就有了 Linux 电源管理子系统,该子系统包含很多方面:什么时候可以降帧、什么时候可以关掉其他 CPU core、系统运行时如果某外设很少用需要让它运行时休眠、系统休眠时要保证哪些外设可以唤醒系统。

博主今天要讨论的,就是一个按键如何唤醒系统,类似于手机的电源键。

这个功能并不是新功能,所以 Linux 内部有一个 demo 可以使用,先教大家如何使用该 demo,然后较大家如何撰写中断唤醒系统驱动。

官方 demo

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

该驱动是专门为按键准备的,是一个身经百战的驱动,任何时候测试按键中断或者中断唤醒系统都可以用它,很多时候比自己写的驱动靠谱。

要想使用该驱动,首先在该目录的 Makefile 中增加:

obj-y  += gpio_keys.o 
  • 1.

设备树中增加:

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; 
  }; 
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

compatible 属性是 “gpio-keys”,gpio_keys.c 文件的674行会匹配这个属性,匹配到了该驱动就会运行。

linux,code 属性是按键值,Linux 对所有按键事件都有编号,所以KEY_ENTER 实际是一个数字,是驱动向上层报告的一个按键值。

gpios 属性是标明哪一个 GPIO 口,低电平触发,大家可以自己选一个 GPIO。

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

修改就是这么简单,不过语法要符合各位手中的开发板平台。然后编译出内核和设备树文件,下载到板子中。(Linux 内核根目录会有 .config 文件,确保 CONFIG_PM_SLEEP=y 有打开)

如果驱动加载成功,在 /proc/interrupts 中可以看到:

从左往右第一列是软件中断号(唯一)。

第二列是 CPU,表示该中断在该CPU上触发了多少次,多核会有多列。

第三列是中断控制器,imx6ull开发板根中断控制器是GPC,外部中断控制器是gpio-mxc,两者是级联关系。

第四列是硬件中断号,也就是GPIO口编号。

第五列表示该中断是边沿触发还是电平触发。

第六列是中断名称,可以找到一个 GPIO Key Enter,如果驱动加载成功就能看到,如果失败就看不到。

验证方法

在内核中,休眠方式有很多种,可以通过下面命令查看

# cat /sys/power/state 
  • 1.

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

freeze:冻结I/O设备,将它们置于低功耗状态,使处理器进入空闲状态,唤醒最快,耗电比其它standby, mem, disk方式高

standby:除了冻结I/O设备外,还会暂停系统,唤醒较快,耗电比其它 mem, disk方式高

mem:将运行状态数据存到内存,并关闭外设,进入等待模式,唤醒较慢,耗电比disk方式高

disk:将运行状态数据存到硬盘,然后关机,唤醒最慢

示例:

# echo mem > /sys/power/state 
  • 1.

系统进入睡眠后,基本都会停掉UI、停掉串口,串口无法操作,如图:

按下按键,系统恢复:

当然这里的 log 并不完整,输入 dmesg 可以看到完整 log:

PM:power manager

具体干了什么,图中有解释,分为 suspend 过程和 resume 过程。

其实一个中断让它支持唤醒系统,最主要是多了两个函数:suspend、resume。

suspend 函数在系统整体 suspend 的时候,会调用每个外设注册的 suspend,我们在这个函数中调用 enable_irq_wake,表示该中断在系统休眠时是 enable 状态。

resume 函数在系统整体 resume 的时候,会调用每个外设注册的 resume 函数,在 resume 函数中调用 disable_irq_wake ,表示该中断在系统运行时不需要。两者成对使用。

具体参看下面文章,写的很好:

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

大家也可以研究一下 gpio_keys.c,该驱动看起来比较复杂,但是很完善,毕竟身经百战,什么因素都考虑到了,测试就用它!

博主写的 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("%s\r\n",__FUNCTION__); 
 return IRQ_HANDLED; 

 
static int gpio_keys_probe(struct platform_device *pdev) 

 int ret = 0; 
 struct device_node *node = NULL;; /* 设备节点*/ 
  
 node = of_find_compatible_node(NULL,NULL,"atkalpha-key"); 
 if (node == NULL){ 
  printk("%s:atkalpha-key node not find!\r\n",__FUNCTION__); 
  return -EINVAL; 
 } 
  
 /* 提取 GPIO */ 
 gpionum = of_get_named_gpio(node,"key-gpio", 0); 
 if (gpionum < 0) { 
   printk("of_get_named_gpio can't get key\r\n"); 
 } 
  
 /* 初始化 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!\r\n", 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("%s\r\n",__FUNCTION__); 
 enable_irq_wake(irqnum); 
 return 0; 

 
static int gpio_keys_resume(struct device *dev) 

 printk("%s\r\n",__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"); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.

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"
}; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

最后再总结一下:中断唤醒系统和普通的驱动区别在于,多了两个函数:suspend 和 resume,在 suspend 函数中,调用 enable_irq_wake,表示该中断号在系统休眠时也是 enable 状态,可以触发中断。在 resume 函数中,调用 disable_irq_wake ,恢复原始的中断触发路径。

然后使用 SIMPLE_DEV_PM_OPS 宏将 suspend 和 resume 函数注册到 gpio_keys_pm_ops 操作集,最终由 platform 注册到系统中。这样完成后,系统休眠过程中就会调用到设备注册的 suspend,系统唤醒过程中就会调用设备注册的 resume 函数。

至于 probe 函数的书写,我在 GPIO 子系统和中断子系统系列文章都讲过这些函数的使用,大家可以去我的网站查看:

http://www.linuxer.vip

note:该 demo 只用来唤醒系统,如果你的中断是在 I2C 等设备驱动中,唤醒系统后要立刻在中断处理函数中进行 I2C 通信,写法不太一样,但是框架相同。

另外,该驱动的中断处理函数中没做什么东西,因此唤醒后执行完中断处理函数后又会睡过去。如果你想要该中断唤醒系统后让系统一直处于唤醒状态,请在中断处理函数中使用 __pm_stay_awake() 和 __pm_relax()函数。

 

责任编辑:姜华 来源: 嵌入式Linux系统开发
相关推荐

2022-01-08 20:04:20

拦截系统调用

2021-07-14 09:00:00

JavaFX开发应用

2011-05-03 15:59:00

黑盒打印机

2011-01-10 14:41:26

2022-07-27 08:16:22

搜索引擎Lucene

2023-04-26 12:46:43

DockerSpringKubernetes

2022-12-07 08:42:35

2022-03-14 14:47:21

HarmonyOS操作系统鸿蒙

2021-12-15 08:49:21

gpio 子系统pinctrl 子系统API

2021-02-26 11:54:38

MyBatis 插件接口

2011-02-22 13:46:27

微软SQL.NET

2021-09-22 08:51:34

Android

2009-07-19 15:02:56

2011-04-28 15:09:15

jQueryjqPlot

2017-07-07 11:01:04

Spark性能调优

2010-09-16 14:08:13

无线双网

2023-03-27 08:28:57

spring代码,starter

2021-07-01 09:31:50

MySQL SQL 语句数据库

2024-04-02 08:58:13

2020-08-12 07:41:39

SQL 优化语句
点赞
收藏

51CTO技术栈公众号