Files
BPI-CM9-Linux/aml-comp/kernel/aml-6.12/common_drivers/drivers/pm/gx_pm.c
T

1142 lines
26 KiB
C

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/pm.h>
#include <linux/suspend.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/psci.h>
#include <linux/errno.h>
#include <asm/suspend.h>
#include <linux/of_address.h>
#include <linux/input.h>
#include <linux/cpuidle.h>
#include <asm/cpuidle.h>
#include <uapi/linux/psci.h>
#include <linux/amlogic/arm-smccc.h>
#include <linux/amlogic/pm.h>
#include <linux/kobject.h>
#include <power.h>
#include <linux/amlogic/power_domain.h>
#include <linux/syscore_ops.h>
#include <linux/amlogic/gki_module.h>
#include <dt-bindings/power/meson-pm.h>
#include <linux/kdebug.h>
#include <linux/panic_notifier.h>
#include <linux/reboot.h>
#include "main.h"
static u32 deep_mode;
static int deep_mode_init_done;
bool is_clr_resume_reason;
static unsigned int suspend_mode;
#define SMCID_POWER_MANAGER 0x82000079
#define SMCSUBID_PM_DDR_ASR 0x1000
#define SMCSUBID_PM_DUMP_INFO 0x1001
#define SMCSUBID_PM_DEEPCTRL 0x1002
#define SMCSUBID_PM_GET_EE_STAT 0x1003
#if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND)
static DEFINE_MUTEX(early_suspend_lock);
static DEFINE_MUTEX(sysfs_trigger_lock);
static LIST_HEAD(early_suspend_handlers);
/* In order to handle legacy early_suspend driver,
* here we export sysfs interface
* for user space to write /sys/power/early_suspend_trigger to trigger
* early_suspend/late resume call back. If user space do not trigger
* early_suspend/late_resume, this op will be done
* by PM_SUSPEND_PREPARE notify.
*/
unsigned int sysfs_trigger;
unsigned int early_suspend_state;
/*
* Avoid run early_suspend/late_resume repeatedly.
*/
unsigned int already_early_suspend;
/* early suspend debug flag */
unsigned int early_suspend_debug;
/* kernel & bl31 & bl30 suspend debug flag */
unsigned int suspend_debug_flag;
/* deep suspend enter flag */
static unsigned int deep_suspend;
/* get power event from input */
static int power_event_available;
/* get device name which send power event */
static char dev_name_event[128];
#define DRIVER_NAME "GX_PM"
void register_early_suspend(struct early_suspend *handler)
{
struct list_head *pos;
mutex_lock(&early_suspend_lock);
list_for_each(pos, &early_suspend_handlers) {
struct early_suspend *e;
e = list_entry(pos, struct early_suspend, link);
if (e->level > handler->level)
break;
}
list_add_tail(&handler->link, pos);
mutex_unlock(&early_suspend_lock);
}
EXPORT_SYMBOL(register_early_suspend);
void unregister_early_suspend(struct early_suspend *handler)
{
mutex_lock(&early_suspend_lock);
list_del(&handler->link);
mutex_unlock(&early_suspend_lock);
}
EXPORT_SYMBOL(unregister_early_suspend);
static int suspend_get_pm_env(char *buf)
{
if (!buf)
return -EINVAL;
if (kstrtoint(buf, 0, &early_suspend_debug)) {
pr_err("early_suspend_debug error: %s\n", buf);
return -EINVAL;
}
return 1;
}
__setup("initcall_debug=", suspend_get_pm_env);
static inline void early_suspend(void)
{
struct early_suspend *pos;
mutex_lock(&early_suspend_lock);
if (!already_early_suspend)
already_early_suspend = 1;
else
goto end_early_suspend;
//set node to 0 notify other drivers begin early suspend
deep_suspend = 0;
if (early_suspend_debug)
pr_info("%s: call handlers\n", __func__);
list_for_each_entry(pos, &early_suspend_handlers, link)
if (pos->suspend) {
if (early_suspend_debug)
pr_info("%s: %ps\n", __func__, pos->suspend);
pos->suspend(pos);
}
if (early_suspend_debug)
pr_info("%s: done\n", __func__);
end_early_suspend:
mutex_unlock(&early_suspend_lock);
}
static inline void late_resume(void)
{
struct early_suspend *pos;
mutex_lock(&early_suspend_lock);
if (already_early_suspend)
already_early_suspend = 0;
else
goto end_late_resume;
if (early_suspend_debug)
pr_info("%s: call handlers\n", __func__);
list_for_each_entry_reverse(pos, &early_suspend_handlers, link)
if (pos->resume) {
if (early_suspend_debug)
pr_info("%s: %ps\n", __func__, pos->resume);
pos->resume(pos);
}
if (early_suspend_debug)
pr_info("%s: done\n", __func__);
end_late_resume:
mutex_unlock(&early_suspend_lock);
}
static ssize_t early_suspend_trigger_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
unsigned int len;
len = sprintf(buf, "%d\n", early_suspend_state);
return len;
}
static ssize_t early_suspend_trigger_store(const struct class *class,
const struct class_attribute *attr,
const char *buf, size_t count)
{
int ret;
ret = kstrtouint(buf, 0, &early_suspend_state);
pr_info("early_suspend_state=%d\n", early_suspend_state);
if (ret)
return -EINVAL;
mutex_lock(&sysfs_trigger_lock);
sysfs_trigger = 1;
if (early_suspend_state == 0)
late_resume();
else if (early_suspend_state == 1)
early_suspend();
mutex_unlock(&sysfs_trigger_lock);
return count;
}
static CLASS_ATTR_RW(early_suspend_trigger);
#define SUSPEND_DEBUG_LOGLEVEL (0xf << (0))
#define SUSPEND_DEBUG_INITCALL_DEBUG (1U << (4))
static unsigned long __invoke_psci_fn_smc(unsigned long function_id,
unsigned long arg0,
unsigned long arg1,
unsigned long arg2);
static const char *suspend_debug_help_str = {
"Usage:\n"
"echo 0xXXXXXXXX > /sys/class/meson_pm/suspend_debug //enable suspend debug mode\n\n"
"Suspend Debug State List:\n\n"
"--------------------------------------------------------------------------------------------------\n"
};
#define MAX_ENTRIES 32
struct Suspend_Debug_Mode {
const char *description;
int end_bit;
int start_bit;
int state;
};
struct Suspend_Debug_Mode suspend_debug_mode[MAX_ENTRIES] = {
{"[BL30] clk/pll status check and show clk gate setting", 31, 31, 0},
{"[BL30] mask RTC Wakeup source", 30, 30, 0},
{"[BL30] mask SARADC Wakeup source", 29, 29, 0},
{"[BL30] mask IR Wakeup source", 28, 28, 0},
{"[BL30] show pwm state and voltage", 27, 27, 0},
{"[BL30] function call log", 26, 26, 0},
{"[BL30] open dmc monitor log", 25, 25, 0},
{"[BL30] bl30 dump boot cpu's fsm and dsu's fsm", 24, 24, 0},
{"[BL30] aocpu do ddr access(reserved)", 23, 23, 0},
{"[BL30] skip power switch(vddee vddcpu vcc5v vcc3.3V etc)", 22, 22, 0},
{"[BL30] skip ddr suspend in bl30", 21, 21, 0},
{"[BL30] start debug task", 20, 20, 0},
{"[BL31] reserved", 19, 17, 0},
{"[BL31] skip clk gate switch", 16, 16, 0},
{"[BL31] dump nonboot cpu's fsm state", 15, 15, 0},
{"[BL31] skip ddr suspend in tlpm", 14, 14, 0},
{"[BL31] power domain clk/pll status check and show clk gate setting", 13, 13, 0},
{"[BL31] do clk reg check and rate check", 12, 12, 0},
{"[BL31] do bl31 ddr checksum in tlpm", 11, 11, 0},
{"[BL31] open dmc monitor log", 10, 10, 0},
{"[BL31] bl31 debug loglevel(0:without log;1: normal;2: function call log)", 9, 8, 0},
{"[KERNEL] reserved for dmc monitor", 7, 4, 0},
{"[KERNEL] set kernel printk loglevel to 0~9", 3, 0, 0},
};
static void check_suspend_debug_mode(void)
{
int i;
for (i = 0; i < MAX_ENTRIES; i++) {
int mask = (1 << (suspend_debug_mode[i].end_bit + 1 -
suspend_debug_mode[i].start_bit)) - 1;
int value = (suspend_debug_flag >> suspend_debug_mode[i].start_bit) & mask;
suspend_debug_mode[i].state = value;
}
}
static void set_suspend_debug_flag(int suspend_flag)
{
__invoke_psci_fn_smc(0x820000F3, 0, suspend_debug_flag &
(~(SUSPEND_DEBUG_LOGLEVEL | SUSPEND_DEBUG_INITCALL_DEBUG)),
0);
}
static ssize_t suspend_debug_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
unsigned int len;
int i;
len = sprintf(&buf[0], "suspend_debug_flag = 0x%x\n\n", suspend_debug_flag);
len += sprintf(&buf[len], "%s\n", suspend_debug_help_str);
check_suspend_debug_mode();
for (i = 0; i < MAX_ENTRIES; i++) {
if (suspend_debug_mode[i].description) {
if (suspend_debug_mode[i].start_bit == suspend_debug_mode[i].end_bit)
len += sprintf(&buf[len], "%-80s bit[%d] \t%d\n",
suspend_debug_mode[i].description,
suspend_debug_mode[i].start_bit,
suspend_debug_mode[i].state);
else
len += sprintf(&buf[len], "%-80s bit[%d:%d] \t%d\n",
suspend_debug_mode[i].description,
suspend_debug_mode[i].end_bit,
suspend_debug_mode[i].start_bit,
suspend_debug_mode[i].state);
}
}
return len;
}
static ssize_t suspend_debug_store(const struct class *class,
const struct class_attribute *attr,
const char *buf, size_t count)
{
int ret;
ret = kstrtouint(buf, 0, &suspend_debug_flag);
pr_info("suspend_debug_flag = 0x%x\n", suspend_debug_flag);
if (ret)
return -EINVAL;
set_suspend_debug_flag(suspend_debug_flag);
return count;
}
static CLASS_ATTR_RW(suspend_debug);
static int suspend_get_debug_env(char *buf)
{
if (!buf)
return -EINVAL;
if (kstrtoint(buf, 0, &suspend_debug_flag)) {
pr_err("suspend_debug_flag error: %s\n", buf);
return -EINVAL;
}
return 1;
}
__setup("suspend_debug=", suspend_get_debug_env);
static void lgcy_early_suspend(void)
{
mutex_lock(&sysfs_trigger_lock);
if (!sysfs_trigger)
early_suspend();
mutex_unlock(&sysfs_trigger_lock);
}
static void lgcy_late_resume(void)
{
mutex_lock(&sysfs_trigger_lock);
if (!sysfs_trigger)
late_resume();
mutex_unlock(&sysfs_trigger_lock);
}
static int lgcy_early_suspend_notify(struct notifier_block *nb,
unsigned long event, void *dummy)
{
if (event == PM_SUSPEND_PREPARE)
lgcy_early_suspend();
if (event == PM_POST_SUSPEND)
lgcy_late_resume();
return NOTIFY_OK;
}
static struct notifier_block lgcy_early_suspend_notifier = {
.notifier_call = lgcy_early_suspend_notify,
};
static unsigned int lgcy_early_suspend_exit(struct platform_device *pdev)
{
int ret;
ret = unregister_pm_notifier(&lgcy_early_suspend_notifier);
return ret;
}
#endif /*CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND*/
typedef unsigned long (psci_fn)(unsigned long, unsigned long,
unsigned long, unsigned long);
static unsigned long __invoke_psci_fn_smc(unsigned long function_id,
unsigned long arg0,
unsigned long arg1,
unsigned long arg2)
{
struct arm_smccc_res res;
arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
return res.a0;
}
static void __iomem *debug_reg;
static void __iomem *exit_reg;
static unsigned int resume_reason;
static unsigned int suspend_reason;
static unsigned int clear_suspend_reason;
static bool is_extd_resume_reason;
/*
* get_resume_value return the register value that stores
* resume reason.
*/
static uint32_t get_resume_value(void)
{
u32 val = 0;
if (exit_reg) {
/* resume reason extension support for new soc such as s4/t3/sc2/s5... */
if (is_extd_resume_reason)
val = readl_relaxed(exit_reg) & 0xff;
/* other soc such as tm2/axg do not support resume reason extension */
else
val = (readl_relaxed(exit_reg) >> 28) & 0xf;
}
return val;
}
/*
* get_resume_reason always return last resume reason.
*/
unsigned int get_resume_reason(void)
{
unsigned int val = 0;
unsigned int reason;
val = get_resume_value();
if (is_extd_resume_reason)
reason = val & 0x7f;
else
reason = val;
return reason;
}
EXPORT_SYMBOL_GPL(get_resume_reason);
/*
* get_resume_method return last resume reason.
* It can be cleared by clr_resume_method().
*/
unsigned int get_resume_method(void)
{
return resume_reason;
}
EXPORT_SYMBOL_GPL(get_resume_method);
/*
* judge whether resume_type need power on lcd during resume.
*
*/
bool is_screenon_resume_method(void)
{
return (get_resume_method() != RTC_WAKEUP) &&
(get_resume_method() != UNDEFINED_WAKEUP) &&
(get_resume_method() != WIFI_WAKEUP) &&
(get_resume_method() != ETH_PHY_WAKEUP);
}
EXPORT_SYMBOL_GPL(is_screenon_resume_method);
static void set_resume_method(unsigned int val)
{
resume_reason = val;
}
static void clr_deep_mode(void)
{
if (deep_mode_init_done)
__invoke_psci_fn_smc(SMCID_POWER_MANAGER,
SMCSUBID_PM_DEEPCTRL, SYSTEM_COLD_BOOT, 0);
}
static void set_deep_mode(void)
{
if (deep_mode_init_done)
__invoke_psci_fn_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_DEEPCTRL, deep_mode, 0);
}
static int clr_suspend_notify(struct notifier_block *nb,
unsigned long event, void *dummy)
{
if (event == PM_SUSPEND_PREPARE) {
set_resume_method(UNDEFINED_WAKEUP);
set_deep_mode();
} else if (event == PM_POST_SUSPEND) {
clr_deep_mode();
}
return NOTIFY_OK;
}
static struct notifier_block clr_suspend_notifier = {
.notifier_call = clr_suspend_notify,
};
unsigned int is_pm_s2idle_mode(void)
{
if (pm_suspend_target_state == PM_SUSPEND_TO_IDLE)
return 1;
else
return 0;
}
EXPORT_SYMBOL_GPL(is_pm_s2idle_mode);
/*Call it as suspend_reason because of historical reasons. */
/*Actually, we should call it wakeup_reason. */
static ssize_t suspend_reason_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
unsigned int len;
unsigned int val;
suspend_reason = get_resume_reason();
val = get_resume_value();
len = sprintf(buf, "%d\nreg val:0x%x\n", suspend_reason, val);
return len;
}
static ssize_t suspend_reason_store(const struct class *class,
const struct class_attribute *attr,
const char *buf, size_t count)
{
int ret;
ret = kstrtouint(buf, 0, &suspend_reason);
if (ret)
return -EINVAL;
return count;
}
static CLASS_ATTR_RW(suspend_reason);
/*Call it as deep_suspend flag when it's val is 1 means last str is deep suspend */
ssize_t deep_suspend_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
return sprintf(buf, "%d\n", deep_suspend);
}
static CLASS_ATTR_RO(deep_suspend);
struct suspend_cmd_t {
const char *name;
int (*store)(void *data, const char *buf, size_t count);
int (*show)(void *data, char *buf);
};
struct suspend_mode_cmd_t {
const char *version;
struct suspend_cmd_t cmds[];
};
static int suspend_ddr_mode_store(void *data, const char *buf, size_t count)
{
int ret;
ret = kstrtouint(buf, 0, &suspend_mode);
if (ret)
return ret;
__invoke_psci_fn_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_DDR_ASR, suspend_mode, 0);
return 0;
}
static int suspend_ddr_mode_show(void *data, char *buf)
{
(void)data;
return scnprintf(buf, PAGE_SIZE, "DDR mode=%s\n", suspend_mode ? "ASR" : "SelfRefresh");
}
bool ee_support_powerdown(void)
{
struct arm_smccc_res res;
arm_smccc_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_GET_EE_STAT, 0, 0, 0, 0, 0, 0, &res);
return res.a0 ? 0 : res.a1;
}
EXPORT_SYMBOL_GPL(ee_support_powerdown);
static int suspend_ctrl_store(void *data, const char *buf, size_t count)
{
char cmd[16];
u32 arg1;
(void)data;
if (!deep_mode_init_done)
return 0;
if (sscanf(buf, "%15s %x", cmd, &arg1) == 2) {
deep_mode = SET_MODULE_VAL(VERSION, PM_VERSION, arg1);
} else {
return -EINVAL;
}
return 0;
}
static int suspend_ctrl_show(void *data, char *buf)
{
(void)data;
if (!deep_mode_init_done)
return 0;
return scnprintf(buf, PAGE_SIZE, "SUSPEND MODE=0x%x\n", deep_mode);
}
static inline int deep_mode_init(struct platform_device *pdev)
{
if (!of_property_read_u32(pdev->dev.of_node, "deep_mode", &deep_mode)) {
//__invoke_psci_fn_smc(SMCID_POWER_MANAGER, SMCSUBID_PM_DEEPCTRL, deep_mode, 0);
deep_mode_init_done = 1;
}
clr_deep_mode();
return 0;
}
static struct suspend_mode_cmd_t suspend_cmds = {
.version = "V01",
.cmds = {
{"0", suspend_ddr_mode_store, suspend_ddr_mode_show},
{"1", suspend_ddr_mode_store, NULL},
{"DEEPCTRL", suspend_ctrl_store, suspend_ctrl_show},
{NULL, NULL, NULL},
}
};
static ssize_t suspend_mode_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
unsigned int len = 0;
int i;
if (!deep_mode_init_done) {
return scnprintf(buf, PAGE_SIZE, "Don't support deep mode config.\n");
}
for (i = 0; suspend_cmds.cmds[i].name; i++) {
if (suspend_cmds.cmds[i].show)
len += suspend_cmds.cmds[i].show(&suspend_cmds, buf + len);
}
return len;
}
static ssize_t suspend_mode_store(const struct class *class,
const struct class_attribute *attr,
const char *buf, size_t count)
{
char cmd[32];
int ret, i;
if (sscanf(buf, "%31s", cmd) != 1)
return -EINVAL;
for (i = 0; suspend_cmds.cmds[i].name; i++) {
if (strcmp(cmd, suspend_cmds.cmds[i].name) == 0) {
ret = suspend_cmds.cmds[i].store(&suspend_cmds, buf, count);
if (ret)
return ret;
return count;
}
}
pr_warn("unknown command:%s\n", cmd);
return -EINVAL;
}
static int panic_notify(struct notifier_block *self,
unsigned long cmd, void *ptr)
{
clr_deep_mode();
return NOTIFY_DONE;
}
static CLASS_ATTR_RW(suspend_mode);
static struct notifier_block panic_notifier = {
.notifier_call = panic_notify,
};
static ssize_t time_out_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
unsigned int val = 0, len;
val = readl_relaxed(debug_reg);
len = sprintf(buf, "%d\n", val);
return len;
}
static int sys_time_out;
static ssize_t time_out_store(const struct class *class,
const struct class_attribute *attr,
const char *buf, size_t count)
{
unsigned int time_out;
int ret;
ret = kstrtouint(buf, 10, &time_out);
switch (ret) {
case 0:
sys_time_out = time_out;
writel_relaxed(time_out, debug_reg);
break;
default:
return -EINVAL;
}
return count;
}
static CLASS_ATTR_RW(time_out);
/* The sys file interface: read state*/
static ssize_t power_event_state_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
char def[] = "NULL";
char msg[] = "POWER_PRESSED";
unsigned int len = -1;
if (power_event_available == 1) {
len = snprintf(buf, sizeof(msg) + 1, "%s\n", msg);
return len;
}
len = snprintf(buf, sizeof(def) + 1, "%s\n", def);
return len;
}
static ssize_t power_event_state_store(const struct class *class,
const struct class_attribute *attr,
const char *buf, size_t count)
{
char cmd[16];
memset(cmd, 0, sizeof(cmd));
size_t copy_len = min(count, sizeof(cmd) - 1);
if (!buf) {
pr_err("input buf is null.\n");
return -EFAULT;
}
memcpy(cmd, buf, copy_len);
cmd[copy_len] = '\0';
// check input command: "clear" or "reset"
if (strncmp(cmd, "clear", 5) == 0 || strncmp(cmd, "reset", 5) == 0) {
power_event_available = 0;
pr_info("Event flag cleared by user write\n");
return copy_len;
}
return -EFAULT;
}
static CLASS_ATTR_RW(power_event_state);
/* The sys file interface: read name*/
static ssize_t power_event_name_show(const struct class *class,
const struct class_attribute *attr,
char *buf)
{
char def[] = "NULL";
unsigned int len = -1;
if (power_event_available == 1) {
len = snprintf(buf, sizeof(dev_name_event) + 1, "%s\n", dev_name_event);
return len;
}
len = snprintf(buf, sizeof(def) + 1, "%s\n", def);
return len;
}
static ssize_t power_event_name_store(const struct class *class,
const struct class_attribute *attr,
const char *buf, size_t count)
{
return -EFAULT;
}
static CLASS_ATTR_RW(power_event_name);
static struct attribute *meson_pm_attrs[] = {
&class_attr_suspend_reason.attr,
&class_attr_suspend_mode.attr,
&class_attr_time_out.attr,
&class_attr_deep_suspend.attr,
#if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND)
&class_attr_early_suspend_trigger.attr,
#endif
&class_attr_suspend_debug.attr,
&class_attr_power_event_state.attr,
&class_attr_power_event_name.attr,
NULL,
};
ATTRIBUTE_GROUPS(meson_pm);
static struct class meson_pm_class = {
.name = "meson_pm",
.class_groups = meson_pm_groups,
};
/* power down action */
static void do_custom_power_action(void)
{
power_event_available = 1;
}
/* Input Handler: do event */
static void power_handler_event(struct input_handle *handle,
unsigned int type,
unsigned int code, int value)
{
size_t copy_len;
if (type == EV_KEY && code == KEY_POWER && value == 1) {
if (handle && handle->dev && handle->dev->name) {
memset(dev_name_event, 0, sizeof(dev_name_event));
copy_len = min(strlen(handle->dev->name), sizeof(dev_name_event) - 1);
memcpy(dev_name_event, handle->dev->name, copy_len);
dev_name_event[copy_len] = '\0';
}
pr_info("Power key pressed from device: (%s)\n", dev_name_event);
do_custom_power_action();
}
}
/* connect function: when handler match devices */
static int power_handler_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *id)
{
struct input_handle *handle;
int ret;
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
handle->dev = dev;
handle->handler = handler;
handle->name = DRIVER_NAME;
ret = input_open_device(handle);
if (ret) {
pr_err("%s: Failed to open device %s\n", DRIVER_NAME, dev->name);
kfree(handle);
return ret;
}
ret = input_register_handle(handle);
if (ret) {
input_close_device(handle);
kfree(handle);
return ret;
}
pr_info("%s: Connected to device: %s\n", DRIVER_NAME, dev->name);
return 0;
}
/* disconnect devices */
static void power_handler_disconnect(struct input_handle *handle)
{
pr_info("%s: Disconnected from device: %s\n", DRIVER_NAME, handle->dev->name);
input_close_device(handle);
input_unregister_handle(handle);
kfree(handle);
}
/* match all input devices */
static const struct input_device_id power_handler_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_KEYBIT,
.keybit = { [BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER) },
.driver_info = 1 },
{ },
};
MODULE_DEVICE_TABLE(input, power_handler_ids);
/* Input Handler */
static struct input_handler power_input_handler = {
.event = power_handler_event,
.connect = power_handler_connect,
.disconnect = power_handler_disconnect,
.name = DRIVER_NAME,
.id_table = power_handler_ids,
};
static int gx_pm_syscore_suspend(void)
{
if (sys_time_out)
writel_relaxed(sys_time_out, debug_reg);
return 0;
}
/*clear wakeup reason*/
static void gx_pm_syscore_shutdown(void)
{
u32 val;
if (exit_reg && is_extd_resume_reason) {
val = readl_relaxed(exit_reg);
if ((val & 0x7f) == REMOTE_CUS_WAKEUP) {
/*Defalut clear netflix wakeup reason*/
val &= ~REMOTE_CUS_WAKEUP;
} else if (clear_suspend_reason) {
if ((val & 0x7f) == clear_suspend_reason)
val &= ~clear_suspend_reason;
}
writel_relaxed(val, exit_reg);
}
}
static void gx_pm_syscore_resume(void)
{
sys_time_out = 0;
set_resume_method(get_resume_reason());
}
static struct syscore_ops gx_pm_syscore_ops = {
.suspend = gx_pm_syscore_suspend,
.resume = gx_pm_syscore_resume,
.shutdown = gx_pm_syscore_shutdown,
};
static int __init gx_pm_init_ops(void)
{
register_syscore_ops(&gx_pm_syscore_ops);
return 0;
}
static int do_aml_reboot_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
switch (event) {
case SYS_HALT:
break;
case SYS_POWER_OFF:
set_deep_mode();
break;
case SYS_RESTART:
break;
}
return NOTIFY_DONE;
}
static struct notifier_block aml_reboot_notifier = {
.notifier_call = do_aml_reboot_notifier,
};
static int meson_pm_probe(struct platform_device *pdev)
{
unsigned int irq_pwrctrl;
int err;
power_event_available = 0;
if (!of_property_read_u32(pdev->dev.of_node,
"irq_pwrctrl", &irq_pwrctrl)) {
pwr_ctrl_irq_set(irq_pwrctrl, 1, 0);
}
if (of_property_read_bool(pdev->dev.of_node, "extend_resume_reason"))
is_extd_resume_reason = true;
else
is_extd_resume_reason = false;
if (of_property_read_u32(pdev->dev.of_node, "clear_suspend_reason",
&clear_suspend_reason)) {
dev_info(&pdev->dev, "clear_suspend_reason not found, using default\n");
clear_suspend_reason = 0;
} else {
dev_info(&pdev->dev, "clear_suspend_reason found: 0x%x\n", clear_suspend_reason);
}
deep_mode_init(pdev);
debug_reg = of_iomap(pdev->dev.of_node, 0);
if (!debug_reg)
return -ENOMEM;
exit_reg = of_iomap(pdev->dev.of_node, 1);
if (!exit_reg)
return -ENOMEM;
set_resume_method(get_resume_reason());
err = class_register(&meson_pm_class);
if (unlikely(err))
return err;
// register input handler
err = input_register_handler(&power_input_handler);
if (err) {
pr_err("%s: Failed to register input handler\n", DRIVER_NAME);
return err;
}
gx_pm_init_ops();
#if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND)
err = register_pm_notifier(&lgcy_early_suspend_notifier);
if (unlikely(err))
return err;
#endif
if (of_property_read_bool(pdev->dev.of_node, "clr_resume_reason"))
is_clr_resume_reason = true;
else
is_clr_resume_reason = false;
err = register_pm_notifier(&clr_suspend_notifier);
if (unlikely(err))
return err;
err = register_die_notifier(&panic_notifier);
if (err != 0)
return err;
/* Register a call for panic conditions. */
err = atomic_notifier_chain_register(&panic_notifier_list,
&panic_notifier);
if (err != 0)
return err;
/* Register a reboot_notifier*/
err = register_reboot_notifier(&aml_reboot_notifier);
if (err != 0)
return err;
if (suspend_debug_flag)
set_suspend_debug_flag(suspend_debug_flag);
return 0;
}
static __maybe_unused void meson_pm_remove(struct platform_device *pdev)
{
if (debug_reg)
iounmap(debug_reg);
if (exit_reg)
iounmap(exit_reg);
class_unregister(&meson_pm_class);
input_unregister_handler(&power_input_handler);
#if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND)
lgcy_early_suspend_exit(pdev);
#endif
}
static const struct of_device_id amlogic_pm_dt_match[] = {
{.compatible = "amlogic, pm",
},
{}
};
static void meson_pm_shutdown(struct platform_device *pdev)
{
u32 val;
if (exit_reg && is_clr_resume_reason &&
is_extd_resume_reason) {
val = readl_relaxed(exit_reg);
val &= (~0x7f);
writel_relaxed(val, exit_reg);
}
}
static int pm_platform_resume(struct device *dev)
{
return 0;
}
static int pm_platform_suspend(struct device *dev)
{
deep_suspend = 1;
if (early_suspend_debug)
pr_info("%s: set deep_suspend flag as 1\n", __func__);
return 0;
}
static const struct dev_pm_ops pm_ops = {
.resume = pm_platform_resume,
.suspend = pm_platform_suspend,
};
static struct platform_driver meson_pm_driver = {
.probe = meson_pm_probe,
.driver = {
.name = "pm-meson",
.owner = THIS_MODULE,
.pm = &pm_ops,
.of_match_table = amlogic_pm_dt_match,
},
.remove = __exit_p(meson_pm_remove),
.shutdown = meson_pm_shutdown,
};
int __init pm_init(void)
{
return platform_driver_register(&meson_pm_driver);
}
void __exit pm_exit(void)
{
platform_driver_unregister(&meson_pm_driver);
}