【Android休眠】之PowerKey唤醒源实现【转】
轉自: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
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
轉載于:https://www.cnblogs.com/sky-heaven/p/10340749.html
總結
以上是生活随笔為你收集整理的【Android休眠】之PowerKey唤醒源实现【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 流言终结者:大数据工程师真的做不过35岁
- 下一篇: Android隐藏EditText长按菜