1. 程式人生 > 實用技巧 >【Android休眠】之PowerKey喚醒源實現

【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: