转自:https://blog.csdn.net/u013686019/article/details/53677531

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013686019/article/details/53677531
版本信息:
Linux:3.10
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文件中,比如如下配置:
arch/arm/boot/dts/xxxxx.dts
power-key {
/** 是CPU的哪个Pin */
gpios = <&gpio0 GPIO_A5 GPIO_ACTIVE_LOW>;
/** Key code */
linux,code = <116>;
/** 起个名字 */
label = "power";
/** 该Pin具有wakeup的功能 */
gpio-key,wakeup;
};

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

可知,PowerKey的编码也在该文件中,且编码值为116;一旦按下PowerKey,该值作为键值传到input_event结构体的code成员变量中:
input.h (kernel\include\uapi\linux)
/*
* The event structure itself
*/

struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
之后我们会写个Linux应用程序读取code值。
四、PowerKey驱动
1、PowerKey驱动注册
在我的板上,PowerKey驱动是按照platform_device注册的,对象:
static struct platform_driver keys_device_driver = {
.probe = keys_probe,
.remove = keys_remove,
.driver = {
.name = "xxx-keypad",
.owner = THIS_MODULE,
.of_match_table = xxx_key_match,
#ifdef CONFIG_PM
.pm = &keys_pm_ops,
#endif
}
};

对象注册:
module_platform_driver(keys_device_driver);

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

2、PowerKey驱动实现
贯穿始终的连个结构体:
/**
* 描述Key具有的属性
*/
struct xxx_keys_button {
u32 code; // key code
const char *desc;//key label
    u32 state; //key up & down state
int gpio;
int active_low;
int wakeup;
    struct timer_list timer;
};

/**
* 驱动属性封装
*/
struct xxx_keys_drvdata {
int nbuttons;
bool in_suspend; /* Flag to indicate if we're suspending/resuming */
int result;
struct input_dev *input;
struct xxx_keys_button button[0];
};

(1)驱动从xxx_probe()函数起始,注意代码的注释:
// 省略异常处理代码
static int keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct xxx_keys_drvdata *ddata = NULL;
struct input_dev *input = NULL;
int i, error = 0;
int wakeup, key_num = 0;

// 1、of_get_child_count: 获取pin configuration的数目
key_num = of_get_child_count(np);

// 2、为xxx_keys_drvdata 分配空间
ddata = devm_kzalloc(dev, sizeof(struct xxx_keys_drvdata) +
key_num * sizeof(struct xxx_keys_button), GFP_KERNEL);

// 3、PowerKey是作为Input设备进行注册的,这里为PowerKey分配Input设备空间
input = devm_input_allocate_device(dev);

platform_set_drvdata(pdev, ddata);

// input->name:设备名字,可以通过cat /sys/class/input/eventX/device/name查看
input->name = "xxx-keypad";
input->dev.parent = dev;

input->id.bustype = BUS_HOST; // 总线类型
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
ddata->input = input;

ddata->nbuttons = key_num;
// 4、解析之前的dts文件
error = xxx_keys_parse_dt(ddata, pdev);

struct xxx_keys_button *button = &ddata->button[i];
// 6、code = 116
if (button->code){
setup_timer(&button->timer,
keys_timer, (unsigned long)button);}

// 7、解析dts文件的时候赋值,此处非0
if (button->wakeup)
wakeup = 1;

// 8、__set_bit(code, input->keybit); input->keybit: 存放PowerKey键值
input_set_capability(input, EV_KEY, button->code);

struct xxx_keys_button *button = &ddata->button[i];
int irq;
// 9、->desc:解析dts文件的时候赋值,devm_gpio_request()申请GPIO
error = devm_gpio_request(dev, button->gpio, button->desc ?: "keys");
// 10、PowerKey相连的Pin为输入模式
error = gpio_direction_input(button->gpio);
// 11、设置为中断Pin并获取中断号irq
irq = gpio_to_irq(button->gpio);

/**keys_isr:中断Handler
* 中断触发方式:IRQF_TRIGGER_FALLING下降沿、IRQF_TRIGGER_RISING上升沿
*/
error = devm_request_irq(dev, irq, keys_isr,
(button->active_low)?IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING,
button->desc ? button->desc : "keys", button);
}

// 存放KEY_WAKEUP键值
input_set_capability(input, EV_KEY, KEY_WAKEUP);
// 12、wakeup非0则启用唤醒CPU功能
device_init_wakeup(dev, wakeup);

// 注册Input驱动
error = input_register_device(input);

return error;

fail2:
device_init_wakeup(dev, 0);
fail1:
while (--i >= 0) {
del_timer_sync(&ddata->button[i].timer);
}
fail0:
platform_set_drvdata(pdev, NULL);

return error;
}
这里完成:
数据成员空间分配
数据成员初始化
dts文件中PowerKey配置解析
Input设备驱动注册
启用唤醒功能
作为唤醒源的中断ISR注册
(2)解析dts文件中PowerKey配置
// 解析dts文件中PowerKey配置
static int xxx_keys_parse_dt(struct xxx_keys_drvdata *pdata, struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct device_node *child_node;
int ret, gpio, i =0;
u32 code, flags;;

if(of_property_read_u32(child_node, "linux,code", &code)) {
dev_err(&pdev->dev, "Missing linux,code property in the DT.\n");
ret = -EINVAL;
goto error_ret;
}
pdata->button[i].code = code; // 116
pdata->button[i].desc = of_get_property(child_node, "label", NULL); // "power"

gpio = of_get_gpio_flags(child_node, 0, &flags);
pdata->button[i].gpio = gpio;
pdata->button[i].active_low = flags & OF_GPIO_ACTIVE_LOW;
pdata->button[i].wakeup = !!of_get_property(child_node, "gpio-key,wakeup", NULL);

return 0;
error_ret:
return ret;
}

(3)唤醒源注册
wakeup.c (kernel\drivers\base\power)
/**@dev: Device to handle.
* @enable: Whether or not to enable @dev as a wakeup device.
*/
int device_init_wakeup(struct device *dev, bool enable)
{
int ret = 0;
if (enable) {
// 1、dev->power.can_wakeup = true
device_set_wakeup_capable(dev, true);
// 2、Enable given device to be a wakeup source.
ret = device_wakeup_enable(dev);
} else {
device_set_wakeup_capable(dev, false);
}

return ret;
}

(4)唤醒动作
还记得之前注册的中断处理函数keys_isr?
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
devm_request_irq(dev, irq, keys_isr,(button->active_low)?IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING,
button->desc ? button->desc : "keys", button);

static irqreturn_t keys_isr(int irq, void *dev_id)
{
// 1、获取在keys_probe()建立的xxx_keys_drvdata对象数据
struct xxx_keys_drvdata *pdata = xxx_key_get_drvdata();
// 2、dev_id即evm_request_irq()的最后一个参数,这里就是我们的PowerKey
struct xxx_keys_button *button = (struct xxx_keys_button *)dev_id;
struct input_dev *input = pdata->input;

// 3、具有休眠唤醒功能且处于休眠模式,
if(button->wakeup == 1 && pdata->in_suspend == true){
button->state = 1;
input_event(input, EV_KEY, button->code, button->state);
input_sync(input);
}
// Timer去抖动
mod_timer(&button->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
return IRQ_HANDLED;
}

setup_timer(&button->timer, keys_timer, (unsigned long)button)
static void keys_timer(unsigned long _data)
{
struct xxx_keys_drvdata *pdata = xxx_key_get_drvdata();
struct xxx_keys_button *button = (struct xxx_keys_button *)_data;
struct input_dev *input = pdata->input;
int state;

state = !!((gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low);

if(button->state != state) {
button->state = state;
input_event(input, EV_KEY, button->code, button->state);
input_event(input, EV_KEY, button->code, button->state);
input_sync(input);
}

if(state)
mod_timer(&button->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
}

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

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

五、PowerKey 事件读取
#include <stdio.h>
#include <linux/input.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV_PATH "/dev/input/event2" // PowerKey report event node

int main(int argc, char **argv)
{
int event_fd = -1;
struct input_event event = {0};
const size_t read_size = sizeof(struct input_event);

event_fd = open(DEV_PATH, O_RDONLY);
if (event_fd <= 0) {
printf("%s open failed: %s\n", DEV_PATH, strerror(errno));
return -1;
}

while (1) {
if (read(event_fd, &event, read_size) == read_size) {
if (event.type == EV_KEY) {
printf("event code: %d\n", event.code);
printf("event value: %d\n", event.value);
} else {
printf("type != EV_KEY, type: %d\n", event.type);
}
}

usleep(10*1000);
}

close(event_fd);
return 0;
}

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

---------------------
作者:__2017__
来源:CSDN
原文:https://blog.csdn.net/u013686019/article/details/53677531
版权声明:本文为博主原创文章,转载请附上博文链接!

【Android休眠】之PowerKey唤醒源实现【转】的更多相关文章

  1. Android休眠唤醒机制简介(二)

    本文转载自:http://blog.csdn.net/zhaoxiaoqiang10_/article/details/24408911 Android休眠唤醒机制简介(二)************* ...

  2. android 休眠唤醒机制分析(二) — early_suspend

    本文转自:http://blog.csdn.net/g_salamander/article/details/7982170 early_suspend是Android休眠流程的第一阶段即浅度休眠,不 ...

  3. android休眠唤醒驱动流程分析【转】

    转自:http://blog.csdn.net/hanmengaidudu/article/details/11777501 标准linux休眠过程: l        power managemen ...

  4. Android休眠唤醒机制简介(一)【转】

    本文转载自:http://blog.csdn.net/zhaoxiaoqiang10_/article/details/24408129 Android休眠唤醒机制简介(一) ************ ...

  5. 【Android休眠】之Android休眠机制

    一.休眠概述 休眠,简而言之就是设备在不需要工作的时候把一些部件.外设关掉(掉电或让它进入低功耗模式). 为什么要休眠呢?一言以蔽之:省电. 休眠分主动休眠和被动休眠.主动休眠:比如我电脑不用了,就通 ...

  6. Android 7.1.1系统源码下载、编译、刷机-Nexus 6实战

    想成为一位合格的Android程序员或者一位Android高级工程师是十分有必要知道Android的框架层的工作原理,要知道其工作原理那么就需要阅读Android的源代码. 想要阅读Android的源 ...

  7. Android Small插件化框架源码分析

    Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...

  8. android应用商店完整版源码

    这个是从一个安卓学习的网站上转载过来的,android应用商店完整版源码,大家可以看看一下吧. _op><ignore_js_op> <ignore_js_op>< ...

  9. Android版的疯狂猜图游戏源码完整版分享

    这个游戏源码是在安装教程网那么分享过来的,Android版的疯狂猜图游戏源码完整版分享,也是本人之前很早以前发的一款游戏源码的,大家如果想了解一下,可以看看吧,不说多了,上一个图先吧.   > ...

随机推荐

  1. JavaSE_坚持读源码_ClassLoader对象_Java1.7

    ClassLoader java.lang public abstract class ClassLoader extends Object //类加载器的责任就是加载类,说了跟没说一样 A clas ...

  2. mysql 遍历所有的库并根据表和sql语句备份

    建库.用户语句 create database test_hb; create user ' test_hb'@'%' identified by '123456'; grant all privil ...

  3. Mysql:性能优化

    性能优化 优化MySQL数据库是数据库管理员和数据库开发人员的必备技能.MySQL优化,一方面是找出系统的瓶颈,提高MySQL数据库的整体性能:一方面需要合理的结构设计和参数调整,以提高用户操作响应的 ...

  4. Windows2012 要远程登录,你需要具有通过远程桌面服务进行登录的权限.

    直接说正题... 提示如下: 解决办法: gpedit进入策略组:计算机配置->Windows设置->安全设置->本地策略->用户权限分配,找到拒绝通过远程桌面服务登录,把里面 ...

  5. golang channle 管道

    管道的使用介绍 现在要计算 1-N 的各个数的阶乘,并且把各个数的阶乘放入到 map 中.最后显示出来.要求使用 goroutine 完成 package main import ( "fm ...

  6. protobuf使用简介

    官网:https://github.com/google/protobuf 环境:windows,java 1. protobuf概述protobuf是Google开发一种数据描述格式,能够将结构化数 ...

  7. JAVA线程池ScheduledExecutorService周期性地执行任务 与单个Thread周期性执行任务的异常处理

    本文记录: 1,使用ScheduledExecutorService的 scheduleAtFixedRate 方法执行周期性任务的过程,讨论了在任务周期执行过程中出现了异常,会导致周期任务失败. 2 ...

  8. MySQL 字符集问题

    MySQL 支持许多字符集及其编码方案, 甚至是不同编码之间的转换. 在使用 MySQL 进行应用程序编程时, 常常会出现乱码现象, 这通常是由于客户端没有声明与 MySQL 服务器通信的字符串编码造 ...

  9. javascript获取值

    <div id='name'>张三</div> $('#name').val() $(name).val() 以上两个都可以得到值,第一种用的比较多.

  10. 十一、移植优化---CONFIG 优化进 menuconfig(1)

    在移植 JZ2440 中,include/configs/jz2440.h 中有很多config 项都是已经在 uboot 的主配置中已经存在了的,这些配置造成了重复,需要优化. 先将原先的 smdk ...