lineage_kernel_xcoverpro/drivers/samsung/sec_hard_reset_hook.c

265 lines
5.9 KiB
C
Executable File

/*
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/sec_hard_reset_hook.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_gpio.h>
#endif
#include <linux/moduleparam.h>
#ifndef CONFIG_SEC_KEY_NOTIFIER
#include <linux/gpio_keys.h>
#else
#include "sec_key_notifier.h"
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif
struct gpio_key_info {
unsigned int keycode;
int gpio;
bool active_low;
};
static unsigned int hard_reset_keys[] = { KEY_POWER, KEY_VOLUMEDOWN };
static struct gpio_key_info keys_info[ARRAY_SIZE(hard_reset_keys)];
static atomic_t hold_keys = ATOMIC_INIT(0);
static ktime_t hold_time;
static struct hrtimer hard_reset_hook_timer;
static bool hard_reset_occurred;
static int all_pressed;
/* Proc node to enable hard reset */
static bool hard_reset_hook_enable = 1;
module_param_named(hard_reset_hook_enable, hard_reset_hook_enable, bool, 0664);
MODULE_PARM_DESC(hard_reset_hook_enable, "1: Enabled, 0: Disabled");
static bool is_hard_reset_key(unsigned int code)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(hard_reset_keys); i++)
if (code == hard_reset_keys[i])
return true;
return false;
}
static int hard_reset_key_set(unsigned int code)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(hard_reset_keys); i++)
if (code == hard_reset_keys[i])
atomic_or(0x1 << i, &hold_keys);
return atomic_read(&hold_keys);
}
static int hard_reset_key_unset(unsigned int code)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(hard_reset_keys); i++)
if (code == hard_reset_keys[i])
atomic_and(~(0x1) << i, &hold_keys);
return atomic_read(&hold_keys);
}
static int hard_reset_key_all_pressed(void)
{
return (atomic_read(&hold_keys) == all_pressed);
}
static int get_gpio_info(unsigned int code, int *gpio, bool *active_low)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(keys_info); i++)
if (code == keys_info[i].keycode) {
*gpio = keys_info[i].gpio;
*active_low = keys_info[i].active_low;
return 0;
}
return -1;
}
static bool is_pressed_gpio_key(int gpio, bool active_low)
{
if (!gpio_is_valid(gpio))
return false;
if ((gpio_get_value(gpio) ? 1 : 0) ^ active_low)
return true;
else
return false;
}
static bool is_gpio_keys_all_pressed(void)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(hard_reset_keys); i++) {
int gpio;
bool active_low;
if (get_gpio_info(hard_reset_keys[i], &gpio, &active_low))
return false;
pr_info("%s:key:%d, gpio:%d, active:%d\n", __func__,
hard_reset_keys[i], gpio, active_low);
if (!is_pressed_gpio_key(gpio, active_low)) {
pr_warn("[%d] is not pressed\n", hard_reset_keys[i]);
return false;
}
}
return true;
}
static enum hrtimer_restart hard_reset_hook_callback(struct hrtimer *hrtimer)
{
if (!is_gpio_keys_all_pressed()) {
pr_warn("All gpio keys are not pressed\n");
return HRTIMER_NORESTART;
}
pr_err("Hard Reset\n");
hard_reset_occurred = true;
BUG();
return HRTIMER_RESTART;
}
static int load_gpio_key_info(void)
{
#ifdef CONFIG_OF
size_t i;
struct device_node *np, *pp;
static int nk;
np = of_find_node_by_path("/gpio_keys");
if (!np)
return -1;
for_each_child_of_node(np, pp) {
uint keycode = 0;
if (!of_find_property(pp, "gpios", NULL))
continue;
if (of_property_read_u32(pp, "linux,code", &keycode))
continue;
for (i = 0; i < ARRAY_SIZE(hard_reset_keys); ++i) {
if (keycode == hard_reset_keys[i]) {
enum of_gpio_flags flags;
keys_info[nk].keycode = keycode;
keys_info[nk].gpio = of_get_gpio_flags(pp, 0, &flags);
if (gpio_is_valid(keys_info[nk].gpio))
keys_info[nk].active_low = flags & OF_GPIO_ACTIVE_LOW;
nk++;
break;
}
}
}
of_node_put(np);
#endif
return 0;
}
static int hard_reset_hook(struct notifier_block *nb,
unsigned long type, void *data)
{
#ifndef CONFIG_SEC_KEY_NOTIFIER
unsigned int code = (unsigned int)type;
int pressed = *(int *)data;
#else
struct sec_key_notifier_param *param = data;
unsigned int code = param->keycode;
int pressed = param->down;
#endif
if (unlikely(!hard_reset_hook_enable))
return NOTIFY_DONE;
if (!is_hard_reset_key(code))
return NOTIFY_DONE;
if (pressed)
hard_reset_key_set(code);
else
hard_reset_key_unset(code);
if (hard_reset_key_all_pressed()) {
hrtimer_start(&hard_reset_hook_timer,
hold_time, HRTIMER_MODE_REL);
pr_info("%s : hrtimer_start\n", __func__);
}
else {
hrtimer_try_to_cancel(&hard_reset_hook_timer);
}
return NOTIFY_OK;
}
#ifndef CONFIG_SEC_KEY_NOTIFIER
static struct notifier_block nb_gpio_keys = {
.notifier_call = hard_reset_hook
};
#else
static struct notifier_block seccmn_hard_reset_notifier = {
.notifier_call = hard_reset_hook
};
#endif
bool is_hard_reset_occurred(void)
{
return hard_reset_occurred;
}
void hard_reset_delay(void)
{
/* HQE team request hard reset key should guarantee 7 seconds.
* To get proper stack, hard reset hook starts after 6 seconds.
* And it will reboot before 7 seconds.
* Add delay to keep the 7 seconds
*/
if (hard_reset_occurred) {
pr_err("wait until PMIC reset occurred");
mdelay(2000);
}
}
int __init hard_reset_hook_init(void)
{
size_t i;
hrtimer_init(&hard_reset_hook_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
hard_reset_hook_timer.function = hard_reset_hook_callback;
hold_time = ktime_set(6, 0); /* 6 seconds */
for (i = 0; i < ARRAY_SIZE(hard_reset_keys); i++)
all_pressed |= 0x1 << i;
load_gpio_key_info();
#ifndef CONFIG_SEC_KEY_NOTIFIER
register_gpio_keys_notifier(&nb_gpio_keys);
#else
sec_kn_register_notifier(&seccmn_hard_reset_notifier,
hard_reset_keys, ARRAY_SIZE(hard_reset_keys));
#endif
return 0;
}
late_initcall(hard_reset_hook_init);