[cpp]   view plain  copy



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







一、唤醒源



设备休眠后,通过触发唤醒源使设备恢复正常工作模式。设备唤醒源有多种,对于Android设备常见的就有PowerKey、来电唤醒、Alarm唤醒等。



唤醒源的实现处于内核空间,本文重点讨论下PowerKey作为唤醒源的具体实现。






二、PowerKey唤醒源



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

android 唤起微信 android唤醒_安卓定制




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




三、PowerKey对应的Pin Configuration


和PowerKey相连的Pin的具体配置位于板级dts文件中,比如 如下 配置:

[cpp]   view plain  copy

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. "power";  
9. /** 该Pin具有wakeup的功能 */  
10.         gpio-key,wakeup;  
11. };


着重说下 linux,code = <116>,116怎么来的?


对于键盘,每一个按键都有唯一的编码,在Linux中,编码值位于:

[cpp]   view plain  copy


    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成员变量中:


    [cpp]   view plain  copy

    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注册的,对象:

    [cpp]   view plain  copy

    1. static struct platform_driver keys_device_driver = {  
    2.     .probe      = keys_probe,  
    3.     .remove     = keys_remove,  
    4.     .driver     = {  
    5. "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. };


    对象注册:

    [cpp]   view plain  copy


    1. module_platform_driver(keys_device_driver);  


    这里遇到了“新伙伴”:之前驱动注册时调用的是“module_init/module_exit”宏,PowerKey驱动注册用“ module_platform_driver”,什么鬼?看下宏注释:

    [cpp]   view plain  copy


    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驱动实现

    贯穿始终的连个结构体:


    [cpp]   view plain  copy


      1. /**
      2.  * 描述Key具有的属性
      3.  */  
      4. struct xxx_keys_button {  
      5. // key code  
      6. const char *desc;//key label  
      7. //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()函数起始,注意代码的注释:

      [cpp]   view plain  copy


      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. sizeof(struct xxx_keys_drvdata) +  
      16. 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. "xxx-keypad";   
      25.     input->dev.parent = dev;  
      26.   
      27. // 总线类型  
      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. 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. "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. "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配置


      [cpp]   view plain  copy


      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. "Missing linux,code property in the DT.\n");  
      11.         ret = -EINVAL;  
      12. goto error_ret;  
      13.     }  
      14. // 116  
      15. "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. "gpio-key,wakeup", NULL);  
      21.   
      22. return 0;  
      23. error_ret:  
      24. return ret;  
      25. }


      (3) 唤醒源注册


      [cpp]   view plain  copy


      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. true);  
      11. // 2、Enable given device to be a wakeup source.  
      12.         ret = device_wakeup_enable(dev);  
      13. else {  
      14. false);  
      15.     }  
      16.   
      17. return ret;  
      18. }


      (4)唤醒动作

      还记得之前注册的中断处理函数keys_isr?

      [cpp]   view plain  copy


      1. devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,   
      2. 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. "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 事件读取


      [cpp]   view plain  copy


      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. "%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. "event code: %d\n", event.code);  
      26. "event value: %d\n", event.value);  
      27. else {  
      28. "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:

      android 唤起微信 android唤醒_键值_02