Files
kernel_common_drivers/drivers/pm/gx_pm.c
T
yiting.deng be4c0fb5e0 pm: fix kernel5.15 pm print log [1/1]
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>
2023-11-09 20:08:16 +08:00

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);
}