【Android休眠】之PowerKey唤醒源实现

  1. 版本信息:  
  2. Linux:3.10  
  3. Android: 4.4  


http://blog.csdn.net/u013686019/article/details/53677531

一、唤醒源

设备休眠后,通过触发唤醒源使设备恢复正常工作模式。设备唤醒源有多种,对于Android设备常见的就有PowerKey、来电唤醒、Alarm唤醒等。
唤醒源的实现处于内核空间,本文重点讨论下PowerKey作为唤醒源的具体实现。

二、PowerKey唤醒源

PowerKey唤醒设备的原理,本质其实就是中断

PowerKey连接到CPU的一个输入(Input)引脚(Pin)上,该Pin运行在中断模式上。一旦PowerKey按下,引发Pin中断;而该中断具有唤醒CPU的功能,于是设备得以唤醒。

三、PowerKey对应的Pin Configuration

和PowerKey相连的Pin的具体配置位于板级dts文件中,比如如下配置:
  1. arch/arm/boot/dts/xxxxx<span style="font-family:Consolas;">.</span>dts  
  2. power-key {  
  3.         /** 是CPU的哪个Pin */  
  4.         gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>;  
  5.         /** Key code  */  
  6.         linux,code = <116>;  
  7.         /** 起个名字 */  
  8.         label = "power";  
  9.         /** 该Pin具有wakeup的功能 */  
  10.         gpio-key,wakeup;  
  11. };  

着重说下linux,code = <116>,116怎么来的?
对于键盘,每一个按键都有唯一的编码,在Linux中,编码值位于:
  1. input.h (kernel\include\uapi\linux)  
  2. /* 
  3.  * Keys and buttons 
  4.  */  
  5. #define KEY_RESERVED    0  
  6. #define KEY_ESC     1  
  7. #define KEY_BACKSPACE   14  
  8. #define KEY_TAB     15  
  9. #define KEY_POWER   116 /* SC System Power Down */  

可知,PowerKey的编码也在该文件中,且编码值为116;一旦按下PowerKey,该值作为键值传到input_event结构体的code成员变量中:
  1. input.h (kernel\include\uapi\linux)  
  2. /* 
  3.  * The event structure itself 
  4.  */  
  5.   
  6. struct input_event {  
  7.     struct timeval time;  
  8.     __u16 type;  
  9.     __u16 code;  
  10.     __s32 value;  
  11. };  
之后我们会写个Linux应用程序读取code值。

四、PowerKey驱动

1、PowerKey驱动注册

在我的板上,PowerKey驱动是按照platform_device注册的,对象:
  1. static struct platform_driver keys_device_driver = {  
  2.     .probe      = keys_probe,  
  3.     .remove     = keys_remove,  
  4.     .driver     = {  
  5.         .name   = "xxx-keypad",  
  6.         .owner  = THIS_MODULE,  
  7.         .of_match_table = xxx_key_match,  
  8. #ifdef CONFIG_PM  
  9.         .pm = &keys_pm_ops,  
  10. #endif  
  11.     }  
  12. };  

对象注册:
  1. module_platform_driver(keys_device_driver);  

这里遇到了“新伙伴”:之前驱动注册时调用的是“module_init/module_exit”宏,PowerKey驱动注册用“module_platform_driver”,什么鬼?看下宏注释:
  1. /* module_platform_driver() - Helper macro for drivers that don't do 
  2.  * anything special in module init/exit.  This eliminates(清除/淘汰) a lot of 
  3.  * boilerplate(样板文件).  Each module may only use this macro once, and 
  4.  * calling it replaces module_init() and module_exit() 
  5.  */  
  6. #define module_platform_driver(__platform_driver) \  
  7.     module_driver(__platform_driver, platform_driver_register, \  
  8.             platform_driver_unregister)  
我们并不需要“module_init/module_exit”宏规定的函数中做什么工作,使用这种方式(注册驱动的模版)注册驱动的话就得准备xxx_init/xxx_exit函数,而采用module_platform_driver注册就免去了这些无用功。

2、PowerKey驱动实现

贯穿始终的连个结构体:
  1. /** 
  2.  * 描述Key具有的属性 
  3.  */  
  4. struct xxx_keys_button {  
  5.     u32 code;  // key code  
  6.     const char *desc;//key label  
  7.     u32 state; //key up & down state  
  8.     int gpio;  
  9.     int active_low;  
  10.     int wakeup;  
  11.     struct timer_list timer;  
  12. };  
  13.   
  14. /** 
  15.  * 驱动属性封装 
  16.  */  
  17. struct xxx_keys_drvdata {  
  18.     int nbuttons;  
  19.     bool in_suspend;    /* Flag to indicate if we're suspending/resuming */  
  20.     int result;  
  21.     struct input_dev *input;  
  22.     struct xxx_keys_button button[0];  
  23. };  

(1)驱动从xxx_probe()函数起始,注意代码的注释:
  1. // 省略异常处理代码  
  2. static int keys_probe(struct platform_device *pdev)  
  3. {  
  4.     struct device *dev = &pdev->dev;  
  5.     struct device_node *np = pdev->dev.of_node;  
  6.     struct xxx_keys_drvdata *ddata = NULL;  
  7.     struct input_dev *input = NULL;  
  8.     int i, error = 0;  
  9.     int wakeup, key_num = 0;  
  10.   
  11.     // 1、of_get_child_count: 获取pin configuration的数目  
  12.     key_num = of_get_child_count(np);  
  13.   
  14.     // 2、为xxx_keys_drvdata 分配空间  
  15.     ddata = devm_kzalloc(dev, sizeof(struct xxx_keys_drvdata) +  
  16.         key_num * sizeof(struct xxx_keys_button), GFP_KERNEL);  
  17.       
  18.     // 3、PowerKey是作为Input设备进行注册的,这里为PowerKey分配Input设备空间  
  19.     input = devm_input_allocate_device(dev);  
  20.   
  21.     platform_set_drvdata(pdev, ddata);  
  22.   
  23.     // input->name:设备名字,可以通过cat /sys/class/input/eventX/device/name查看  
  24.     input->name = "xxx-keypad";   
  25.     input->dev.parent = dev;  
  26.   
  27.     input->id.bustype = BUS_HOST; // 总线类型  
  28.     input->id.vendor = 0x0001;  
  29.     input->id.product = 0x0001;  
  30.     input->id.version = 0x0100;  
  31.     ddata->input = input;  
  32.   
  33.     ddata->nbuttons = key_num;  
  34.     // 4、解析之前的dts文件  
  35.     error = xxx_keys_parse_dt(ddata, pdev);  
  36.   
  37.     struct xxx_keys_button *button = &ddata->button[i];  
  38.     // 6、code = 116  
  39.     if (button->code){  
  40.         setup_timer(&button->timer,  
  41.                 keys_timer, (unsigned long)button);}  
  42.   
  43.     // 7、解析dts文件的时候赋值,此处非0  
  44.     if (button->wakeup)  
  45.         wakeup = 1;  
  46.       
  47.     // 8、__set_bit(code, input->keybit); input->keybit: 存放PowerKey键值  
  48.     input_set_capability(input, EV_KEY, button->code);  
  49.   
  50.     struct xxx_keys_button *button = &ddata->button[i];  
  51.     int irq;  
  52.     // 9、->desc:解析dts文件的时候赋值,devm_gpio_request()申请GPIO  
  53.     error = devm_gpio_request(dev, button->gpio, button->desc ?: "keys");  
  54.     // 10、PowerKey相连的Pin为输入模式  
  55.     error = gpio_direction_input(button->gpio);  
  56.     // 11、设置为中断Pin并获取中断号irq  
  57.     irq = gpio_to_irq(button->gpio);  
  58.   
  59.     /**keys_isr:中断Handler 
  60.      * 中断触发方式:IRQF_TRIGGER_FALLING下降沿、IRQF_TRIGGER_RISING上升沿 
  61.      */  
  62.     error = devm_request_irq(dev, irq, keys_isr,  
  63.         (button->active_low)?IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING,  
  64.         button->desc ? button->desc : "keys", button);  
  65.     }  
  66.   
  67.     // 存放KEY_WAKEUP键值  
  68.     input_set_capability(input, EV_KEY, KEY_WAKEUP);  
  69.     // 12、wakeup非0则启用唤醒CPU功能  
  70.     device_init_wakeup(dev, wakeup);  
  71.   
  72.     // 注册Input驱动  
  73.     error = input_register_device(input);  
  74.   
  75.     return error;  
  76.   
  77.  fail2:  
  78.     device_init_wakeup(dev, 0);  
  79.  fail1:  
  80.     while (--i >= 0) {  
  81.         del_timer_sync(&ddata->button[i].timer);  
  82.     }  
  83.  fail0:  
  84.     platform_set_drvdata(pdev, NULL);  
  85.   
  86.     return error;  
  87. }  
这里完成:
  • 数据成员空间分配
  • 数据成员初始化
  • dts文件中PowerKey配置解析
  • Input设备驱动注册
  • 启用唤醒功能
  • 作为唤醒源的中断ISR注册
(2)解析dts文件中PowerKey配置
  1. // 解析dts文件中PowerKey配置  
  2. static int xxx_keys_parse_dt(struct xxx_keys_drvdata *pdata, struct platform_device *pdev)  
  3. {  
  4.     struct device_node *node = pdev->dev.of_node;  
  5.     struct device_node *child_node;  
  6.     int ret, gpio, i =0;  
  7.     u32 code, flags;;  
  8.   
  9.     if(of_property_read_u32(child_node, "linux,code", &code)) {  
  10.         dev_err(&pdev->dev, "Missing linux,code property in the DT.\n");  
  11.         ret = -EINVAL;  
  12.         goto error_ret;  
  13.     }  
  14.     pdata->button[i].code = code; // 116  
  15.     pdata->button[i].desc = of_get_property(child_node, "label", NULL); // "power"  
  16.   
  17.     gpio = of_get_gpio_flags(child_node, 0, &flags);  
  18.     pdata->button[i].gpio = gpio;  
  19.     pdata->button[i].active_low = flags & OF_GPIO_ACTIVE_LOW;  
  20.     pdata->button[i].wakeup = !!of_get_property(child_node, "gpio-key,wakeup", NULL);  
  21.   
  22.     return 0;  
  23. error_ret:  
  24.     return ret;  
  25. }  

(3)唤醒源注册
  1. wakeup.c (kernel\drivers\base\power)  
  2. /**@dev: Device to handle. 
  3.  * @enable: Whether or not to enable @dev as a wakeup device. 
  4.  */  
  5. int device_init_wakeup(struct device *dev, bool enable)  
  6. {  
  7.     int ret = 0;  
  8.     if (enable) {  
  9.         // 1、dev->power.can_wakeup = true  
  10.         device_set_wakeup_capable(dev, true);  
  11.         // 2、Enable given device to be a wakeup source.  
  12.         ret = device_wakeup_enable(dev);  
  13.     } else {  
  14.         device_set_wakeup_capable(dev, false);  
  15.     }  
  16.   
  17.     return ret;  
  18. }  

(4)唤醒动作
还记得之前注册的中断处理函数keys_isr?
  1. devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,   
  2.     unsigned long irqflags, const char *devname, void *dev_id)  
  3. devm_request_irq(dev, irq, keys_isr,(button->active_low)?IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING,  
  4.     button->desc ? button->desc : "keys", button);  
  5.   
  6. static irqreturn_t keys_isr(int irq, void *dev_id)  
  7. {  
  8.     // 1、获取在keys_probe()建立的xxx_keys_drvdata对象数据  
  9.     struct xxx_keys_drvdata *pdata = xxx_key_get_drvdata();  
  10.     // 2、dev_id即evm_request_irq()的最后一个参数,这里就是我们的PowerKey  
  11.     struct xxx_keys_button *button = (struct xxx_keys_button *)dev_id;  
  12.     struct input_dev *input = pdata->input;  
  13.   
  14.     // 3、具有休眠唤醒功能且处于休眠模式,  
  15.     if(button->wakeup == 1 && pdata->in_suspend == true){  
  16.         button->state = 1;  
  17.         input_event(input, EV_KEY, button->code, button->state);  
  18.         input_sync(input);  
  19.     }  
  20.     // Timer去抖动  
  21.     mod_timer(&button->timer,  
  22.                 jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));  
  23.     return IRQ_HANDLED;  
  24. }  
  25.   
  26. setup_timer(&button->timer, keys_timer, (unsigned long)button)  
  27. static void keys_timer(unsigned long _data)  
  28. {  
  29.     struct xxx_keys_drvdata *pdata = xxx_key_get_drvdata();  
  30.     struct xxx_keys_button *button = (struct xxx_keys_button *)_data;  
  31.     struct input_dev *input = pdata->input;  
  32.     int state;  
  33.       
  34.     state = !!((gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low);  
  35.       
  36.     if(button->state != state) {  
  37.         button->state = state;         
  38.         input_event(input, EV_KEY, button->code, button->state);  
  39.         input_event(input, EV_KEY, button->code, button->state);  
  40.         input_sync(input);  
  41.     }  
  42.   
  43.     if(state)  
  44.         mod_timer(&button->timer,  
  45.             jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));  
  46. }  

如果处于休眠态,直接上报唤醒事件(button->state = 1);否则就需要判断按键状态(keys_timer)。

至此,PowerKey作为唤醒源的实现就完成了。

五、PowerKey 事件读取

  1. #include <stdio.h>    
  2. #include <linux/input.h>    
  3. #include <stdlib.h>    
  4. #include <sys/types.h>    
  5. #include <sys/stat.h>    
  6. #include <fcntl.h>    
  7.     
  8. #define DEV_PATH "/dev/input/event2"   // PowerKey report event node  
  9.   
  10. int main(int argc, char **argv)  
  11. {  
  12.     int event_fd = -1;  
  13.     struct input_event event = {0};  
  14.     const size_t read_size = sizeof(struct input_event);  
  15.   
  16.     event_fd = open(DEV_PATH, O_RDONLY);  
  17.     if (event_fd <= 0) {  
  18.         printf("%s open failed: %s\n", DEV_PATH, strerror(errno));  
  19.         return -1;  
  20.     }  
  21.   
  22.     while (1) {  
  23.         if (read(event_fd, &event, read_size) == read_size) {  
  24.             if (event.type == EV_KEY) {  
  25.                 printf("event code: %d\n", event.code);  
  26.                 printf("event value: %d\n", event.value);  
  27.             } else {  
  28.                 printf("type != EV_KEY, type: %d\n", event.type);  
  29.             }  
  30.         }  
  31.   
  32.         usleep(10*1000);  
  33.     }  
  34.   
  35.     close(event_fd);  
  36.     return 0;  
  37. }  

编译、adb push到Android设备中,运行后操作PowerKey,可见Log:

没有更多推荐了,返回首页