【Android休眠】之PowerKey喚醒源實現
受不了xxxx噁心人的行為,遂搬遷至部落格園。 始發:2016-12-15 22:19:01 版本資訊: Linux:3.10 Android: 4.4
一、喚醒源
裝置休眠後,通過觸發喚醒源使裝置恢復正常工作模式。裝置喚醒源有多種,對於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: