1.休眠方式
在内核中,休眠方式有很多种,可以通过下面命令查看
常用的休眠方式有freeze,standby, mem, disk
- freeze: 冻结I/O设备,将它们置于低功耗状态,使处理器进入空闲状态,唤醒最快,耗电比其它standby, mem, disk方式高
- standby:除了冻结I/O设备外,还会暂停系统,唤醒较快,耗电比其它 mem, disk方式高
- mem: 将运行状态数据存到内存,并关闭外设,进入等待模式,唤醒较慢,耗电比disk方式高
- disk: 将运行状态数据存到硬盘,然后关机,唤醒最慢
示例:
2.唤醒方式
当我们休眠时,如果想唤醒,则需要添加中断唤醒源,使得在休眠时,这些中断是设为开启的,当有中断来,则会退出唤醒,常见的中断源有按键,USB等.
3.以按键驱动为例(基于内核3.10.14)
在内核中,有个input按键子系统“gpio-keys”(位于driver/input/keyboard/gpio.keys.c),该平台驱动platform_driver已经在内核中写好了(后面会简单分析)
我们只需要在内核启动时,注册“gpio-keys”平台设备platform_device,即可实现一个按键驱动.
3.1首先使板卡支持input按键子系统(基于mips君正X1000的板卡)
查看Makefile,找到driver/input/keyboard/gpio.keys.c需要CONFIG_KEYBOARD_GPIO宏
方式1-修改对应板卡的defconfig文件,添加宏:
方式2-进入make menuconfig
3.2修改好后,接下来写my_button.c文件,来注册platform_device
上面的arch_initcall()表示:
会将button_base_init函数放在内核链接脚本.initcall3.init段中,然后在内核启动时,会去读链接脚本,然后找到button_base_init()函数,并执行它.
通常,在内核中,platform 设备的初始化(注册)用arch_initcall()调用
而驱动的注册则用module_init()调用,因为module_init()在arch_initcall()之后才调用
因为在init.h中定义:
3.3然后将my_button.c文件添加到Makefile中
编译内核后,便实现一个简单的按键唤醒休眠了.
接下来开始分析platform_driver(位于driver/input/keyboard/gpio.keys.c),看看是如何注册按键和实现唤醒的.
4.分析driver/input/keyboard/gpio.keys.c
4.1该文件里有常用的函数有
设置按键和input_dev,注册input-key子系统
设置GPIO,设置input结构体支持的按键值,设置中断,设置防抖动机制
按键中断函数,如果是中断源,则通过pm_stay_awake()通知pm子系统唤醒,如果有防抖动,则延时并退出,否则通过schedule_work()来调用gpio_keys_gpio_work_func()一次
定时器超时处理函数,用来实现防抖动,里面会通过schedule_work()来调用一次gpio_keys_gpio_work_func();
处理gpio事件函数,用来上报input事件,并判断按键中断源,如果是的话,则调用pm_relax(),通知pm子系统唤醒工作结束
通知pm(power manager), 唤醒休眠
休眠函数,休眠之前会被调用
唤醒函数,唤醒之前被调用
SIMPLE_DEV_PM_OPS宏位于pm.h,它将会定义一个dev_pm_ops结构体,用来被pm子系统调用,实现休眠唤醒
4.2 首先来看probe函数
如下图所示,probe函数为gpio_keys_probe()
gpio_keys_probe()函数定义如下所示:
4.3其中device_init_wakeup()函数定义如下:
然后休眠唤醒的时候,就会根据dev->power.can_wakeup和dev->power.should_wakeup来做不同的操作
4.4 其中gpio_keys_suspend()休眠函数定义如下所示:
从上面函数可以看到,进入休眠之前,我们需要调用enable_irq_wake()来设置唤醒源
4.5 然后在中断函数中,判断是否需要上报唤醒事件,中断函数如下所示:
其中gpio_keys_gpio_work_func()函数如下所示:
从上面两个函数可以看到,唤醒休眠时,需要使用两个函数实现:
在中断前调用pm_stay_awake(),中断结束时再调用一次pm_relax()函数.
4.6 如果想延时唤醒,也可以使用另一种唤醒休眠,则只需要一个函数实现:
4.7 接下来来看gpio_keys_setup_key(),如何设置按键的(只加了重要的部分)
通过gpio.keys.c,得出唤醒流程:
休眠时:
唤醒后:
中断时,有两种唤醒PM模式
模式1-使用两个函数实现:
- 进入中断时调用一次pm_stay_awake().
- 退出时也调用一次pm_relax(bdata->input->dev.parent);
模式2-只需一个函数实现:
- 进入中断时调用pm_wakeup_event(struct device *dev, unsigned int msec).
5.接下来,我们自己写个按键字符驱动,实现休眠唤醒
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/sched.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_platform.h> #include <linux/of_gpio.h> #include <linux/spinlock.h> #include <soc/gpio.h> #define MYKEY_GPIO GPIO_PB(31) static DECLARE_WAIT_QUEUE_HEAD(mykey_waitqueue); struct mykey_button { unsigned int gpio; const char *desc; int wakeup; /*唤醒源*/ int debounce_interval; /* 防抖动 时间ms*/ int wait_event; /*等待队列事件*/ int key_val; /*按键值*/ int irq; struct timer_list timer; /*防抖动定时器*/ struct work_struct work; struct device *dev; }; static struct mykey_button mykey_data={ .gpio = MYKEY_GPIO, .desc = "mykey", .wakeup = 1, .debounce_interval = 10, //10ms .wait_event = 0, }; static void mykey_func(struct work_struct *work) { struct mykey_button *data = container_of(work, struct mykey_button, work); //通过work成员变量找到父结构体 if(data->wakeup) { pm_wakeup_event(data->dev, 0); } data->key_val =gpio_get_value(data->gpio); data->wait_event =1; wake_up_interruptible(&mykey_waitqueue); //唤醒队列 }
static void mykey_irq_timer(unsigned long _data) { struct mykey_button *data =(struct mykey_button *)_data; schedule_work(&data->work); //调用mykey_func()函数 } static irqreturn_t mykey_irq(int irq, void *dev_id) { struct mykey_button *data = dev_id; if(data->debounce_interval) mod_timer(&data->timer, jiffies+msecs_to_jiffies(10)); else schedule_work(&data->work); return IRQ_HANDLED; } static ssize_t mykey_read(struct file *file, char __user *user, size_t count, loff_t *ppos) { wait_event_interruptible(mykey_waitqueue,mykey_data.wait_event ); //进入等待队列休眠,如果中断来数据,则跳出 copy_to_user(user, &mykey_data.key_val, sizeof(mykey_data.key_val)); mykey_data.wait_event =0; return 0; } static int mykey_open(struct inode *inode, struct file *file) { int err=0; int irq; err=gpio_request_one(mykey_data.gpio, GPIOF_DIR_IN, mykey_data.desc); //获取管脚,并设置管脚为输入 if (err < 0) { printk("mykey_open err : gpio_request_one err=%dn",err); return -EINVAL; } irq = gpio_to_irq(mykey_data.gpio); //获取IRQ中断号,用来注册中断 if(irq<0) { err =irq; printk("mykey_open err : gpio_to_irq err=%dn",irq); goto fail; } mykey_data.irq = irq; INIT_WORK(&mykey_data.work, mykey_func); //初始化工作队列 err=request_irq(irq,mykey_irq,IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING, mykey_data.desc,&mykey_data); if (err) { printk("mykey_open err : request_irq err=%dn",err); goto fail; } if(mykey_data.wakeup) enable_irq_wake(irq); //将引脚设为唤醒源 if(mykey_data.debounce_interval) setup_timer(&mykey_data.timer, mykey_irq_timer, (unsigned long)&mykey_data); //设置定时器return 0; fail: if (gpio_is_valid(mykey_data.gpio)) gpio_free(mykey_data.gpio); return err; }
static int mykey_release(struct inode *inode, struct file *file) { free_irq(mykey_data.irq,&mykey_data); cancel_work_sync(&mykey_data.work); if(mykey_data.wakeup) disable_irq_wake(mykey_data.irq); if(mykey_data.debounce_interval) del_timer_sync(&mykey_data.timer); gpio_free(mykey_data.gpio); return 0; } struct file_operations mykey_ops={ .owner = THIS_MODULE, .open = mykey_open, .read = mykey_read, .release=mykey_release, }; static int major; static struct class *cls; static int mykey_init(void) { struct device *mydev; major=register_chrdev(0,"mykey", &mykey_ops); cls=class_create(THIS_MODULE, "mykey"); mydev = device_create(cls, 0, MKDEV(major,0),&mykey_data,"mykey"); mykey_data.dev = mydev; return 0; } static void mykey_exit(void) { device_destroy(cls, MKDEV(major,0)); class_destroy(cls); unregister_chrdev(major, "mykey"); } module_init(mykey_init); module_exit(mykey_exit); MODULE_LICENSE("GPL");
应用测试代码如下:
试验:
原文链接:https://www.cnblogs.com/lifexy/p/9629699.html
原创文章,作者:优速盾-小U,如若转载,请注明出处:https://www.cdnb.net/bbs/archives/33125