mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
be4c0fb5e0
PD#SWPL-143117 Problem: too much pm related print in suspend/resume Solution: add initcall_debug judgment before suspend/resume printing Verify: sc2 Change-Id: Ifbca9604ae008b188da408cd8c3cc353f0d3051f Signed-off-by: yiting.deng <yiting.deng@amlogic.com>
536 lines
11 KiB
C
536 lines
11 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/arm-smccc.h>
|
|
#include <linux/amlogic/pm.h>
|
|
#include <linux/kobject.h>
|
|
#include <../kernel/power/power.h>
|
|
#include <linux/amlogic/power_domain.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/amlogic/gki_module.h>
|
|
|
|
bool is_clr_resume_reason;
|
|
|
|
#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;
|
|
|
|
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 0;
|
|
}
|
|
__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;
|
|
|
|
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(struct class *class,
|
|
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(struct class *class,
|
|
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);
|
|
|
|
void lgcy_early_suspend(void)
|
|
{
|
|
mutex_lock(&sysfs_trigger_lock);
|
|
|
|
if (!sysfs_trigger)
|
|
early_suspend();
|
|
|
|
mutex_unlock(&sysfs_trigger_lock);
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
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 suspend_state_t pm_state;
|
|
static unsigned int resume_reason;
|
|
static unsigned int 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);
|
|
|
|
static void set_resume_method(unsigned int val)
|
|
{
|
|
resume_reason = val;
|
|
}
|
|
|
|
static int clr_suspend_notify(struct notifier_block *nb,
|
|
unsigned long event, void *dummy)
|
|
{
|
|
if (event == PM_SUSPEND_PREPARE)
|
|
set_resume_method(UNDEFINED_WAKEUP);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block clr_suspend_notifier = {
|
|
.notifier_call = clr_suspend_notify,
|
|
};
|
|
|
|
unsigned int is_pm_s2idle_mode(void)
|
|
{
|
|
if (pm_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. */
|
|
ssize_t suspend_reason_show(struct class *class,
|
|
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;
|
|
}
|
|
|
|
ssize_t suspend_reason_store(struct class *class,
|
|
struct class_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int ret;
|
|
|
|
ret = kstrtouint(buf, 0, &suspend_reason);
|
|
|
|
switch (ret) {
|
|
case 1:
|
|
__invoke_psci_fn_smc(0x82000042, suspend_reason, 0, 0);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static CLASS_ATTR_RW(suspend_reason);
|
|
|
|
ssize_t time_out_show(struct class *class, 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;
|
|
ssize_t time_out_store(struct class *class, 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);
|
|
|
|
static struct attribute *meson_pm_attrs[] = {
|
|
&class_attr_suspend_reason.attr,
|
|
&class_attr_time_out.attr,
|
|
#if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND)
|
|
&class_attr_early_suspend_trigger.attr,
|
|
#endif
|
|
NULL,
|
|
};
|
|
|
|
ATTRIBUTE_GROUPS(meson_pm);
|
|
|
|
static struct class meson_pm_class = {
|
|
.name = "meson_pm",
|
|
.owner = THIS_MODULE,
|
|
.class_groups = meson_pm_groups,
|
|
};
|
|
|
|
int gx_pm_syscore_suspend(void)
|
|
{
|
|
if (sys_time_out)
|
|
writel_relaxed(sys_time_out, debug_reg);
|
|
return 0;
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
static int __init gx_pm_init_ops(void)
|
|
{
|
|
register_syscore_ops(&gx_pm_syscore_ops);
|
|
return 0;
|
|
}
|
|
|
|
static int meson_pm_probe(struct platform_device *pdev)
|
|
{
|
|
unsigned int irq_pwrctrl;
|
|
int err;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
err = class_register(&meson_pm_class);
|
|
if (unlikely(err))
|
|
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;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __exit meson_pm_remove(struct platform_device *pdev)
|
|
{
|
|
if (debug_reg)
|
|
iounmap(debug_reg);
|
|
if (exit_reg)
|
|
iounmap(exit_reg);
|
|
|
|
class_unregister(&meson_pm_class);
|
|
|
|
#if IS_ENABLED(CONFIG_AMLOGIC_LEGACY_EARLY_SUSPEND)
|
|
lgcy_early_suspend_exit(pdev);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
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 struct platform_driver meson_pm_driver = {
|
|
.probe = meson_pm_probe,
|
|
.driver = {
|
|
.name = "pm-meson",
|
|
.owner = THIS_MODULE,
|
|
.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);
|
|
}
|