1
0
mirror of https://github.com/physwizz/a155-U-u1.git synced 2025-08-15 06:35:18 +00:00
Files
a155-U-u1/kernel-5.10/drivers/samsung/pm/sec_pm_debug.c
physwizz 99537be4e2 first
2024-03-11 06:53:12 +11:00

257 lines
5.8 KiB
C

/*
* sec_pm_debug.c
*
* Copyright (c) 2022 Samsung Electronics Co., Ltd.
* http://www.samsung.com
* Jonghyeon Cho <jongjaaa.cho.cho@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/module.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/sec_pm_debug.h>
#include <linux/pm_wakeup.h>
#include <linux/of.h>
#include <linux/thermal.h>
#include <linux/suspend.h>
#include <linux/rtc.h>
#ifdef KLOG_MODNAME
#undef KLOG_MODNAME
#define KLOG_MODNAME ""
#endif
static struct sec_pm_dbg pm;
static void sec_pm_suspend_marker(char *type)
{
struct timespec64 ts;
struct rtc_time tm;
ktime_get_real_ts64(&ts);
rtc_time64_to_tm(ts.tv_sec, &tm);
pr_info("PM: suspend %s %d-%02d-%02d %02d:%02d:%02d.%09lu UTC\n",
type, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
}
static void sec_pm_debug_print_tz(void)
{
int i, temp, len = 0;
struct thermal_zone_device *tz;
char buf[500] = {0, };
len += snprintf(buf + len, sizeof(buf) - len, "tz_print");
for (i = 0; i < pm.tz_cnt; i++) {
tz = thermal_zone_get_zone_by_name(pm.tz_list[i]);
if (!tz) {
pr_err("%s: fail to get thermal zone %s\n", __func__, pm.tz_list[i]);
continue;
}
thermal_zone_get_temp(tz, &temp);
len += snprintf(buf + len, sizeof(buf) - len,
"[%s:%d]", pm.tz_list[i], (int)temp / 100);
}
pr_info("%s\n", buf);
}
static void print_wakeup_source_active(
struct wakeup_source *ws)
{
unsigned long flags;
ktime_t total_time;
unsigned long active_count;
ktime_t active_time;
ktime_t prevent_sleep_time;
spin_lock_irqsave(&ws->lock, flags);
total_time = ws->total_time;
prevent_sleep_time = ws->prevent_sleep_time;
active_count = ws->active_count;
if (ws->active) {
ktime_t now = ktime_get();
active_time = ktime_sub(now, ws->last_time);
total_time = ktime_add(total_time, active_time);
if (ws->autosleep_enabled)
prevent_sleep_time = ktime_add(prevent_sleep_time,
ktime_sub(now, ws->start_prevent_time));
} else {
active_time = ktime_set(0, 0);
}
pr_info("%s: active_count(%lu), active_time(%lld), total_time(%lld)\n",
ws->name, active_count,
ktime_to_ms(active_time), ktime_to_ms(total_time));
spin_unlock_irqrestore(&ws->lock, flags);
}
static int wakeup_sources_stats_active(void)
{
struct wakeup_source *ws;
int idx;
pr_info("[SEC_PM] Active wake lock:\n");
idx = wakeup_sources_read_lock();
for_each_wakeup_source(ws)
if (ws->active)
print_wakeup_source_active(ws);
wakeup_sources_read_unlock(idx);
return 0;
}
static void handle_tz_work(struct work_struct *work)
{
sec_pm_debug_print_tz();
schedule_delayed_work(&pm.tz_work, msecs_to_jiffies(pm.tz_work_ms));
}
static void handle_ws_work(struct work_struct *work)
{
wakeup_sources_stats_active();
schedule_delayed_work(&pm.ws_work, msecs_to_jiffies(pm.ws_work_ms));
}
static int sec_pm_notifier_event(struct notifier_block *notifier,
unsigned long pm_event, void *unused)
{
switch (pm_event) {
case PM_SUSPEND_PREPARE:
sec_pm_suspend_marker("entry");
break;
case PM_POST_SUSPEND:
sec_pm_suspend_marker("exit");
break;
default:
break;
}
return NOTIFY_DONE;
}
static struct notifier_block sec_pm_notifier_block = {
.notifier_call = sec_pm_notifier_event,
};
static int sec_pm_debug_parse_dt(struct device_node *np)
{
int ret = 0, cnt = 0;
struct property *prop;
const char *tz;
u32 val;
if (!np) {
pr_err("%s: no device tree\n", __func__);
ret = -EINVAL;
goto parse_fail;
}
if (!of_property_read_u32(np, "ws_work_ms", &val))
pm.ws_work_ms = val;
else
pm.ws_work_ms = DEFAULT_LOGGING_MS;
if (!of_property_read_u32(np, "tz_work_ms", &val))
pm.tz_work_ms = val;
else
pm.tz_work_ms = DEFAULT_LOGGING_MS;
if (of_property_count_strings(np, "thermal_zones") < 0) {
pr_err("%s: cannot thermal_zones property\n", __func__);
ret = -EINVAL;
goto parse_fail;
}
of_property_for_each_string(np, "thermal_zones", prop, tz) {
pm.tz_list[cnt++] = (char *)tz;
}
pm.tz_cnt = cnt;
pr_info("%s: register %d thermal zones\n", __func__, cnt);
parse_fail:
return ret;
}
static int sec_pm_debug_probe(struct platform_device *pdev)
{
int ret = 0;
pr_info("%s: start\n", __func__);
/* Parse Device Tree */
ret = sec_pm_debug_parse_dt(pdev->dev.of_node);
/* Initiate wakeup sorce workqueue */
INIT_DELAYED_WORK(&pm.ws_work, handle_ws_work);
if (!schedule_delayed_work(&pm.ws_work, msecs_to_jiffies(5000))) {
pr_info("%s: Fail to register workqueue\n", __func__);
ret = -EAGAIN;
goto probe_fail;
}
/* Initiate thermal zone workqueue */
INIT_DELAYED_WORK(&pm.tz_work, handle_tz_work);
if (!schedule_delayed_work(&pm.tz_work, msecs_to_jiffies(5000))) {
pr_info("%s: Fail to register workqueue\n", __func__);
ret = -EAGAIN;
goto probe_fail;
}
/* Register PM notifier */
ret = register_pm_notifier(&sec_pm_notifier_block);
if (ret) {
pr_info("%s: Fail to register PM notifier(%d)\n", ret);
goto probe_fail;
}
pr_info("%s: done\n", __func__);
probe_fail:
return ret;
}
static const struct of_device_id sec_pm_debug_match_table[] = {
{ .compatible = "samsung,sec_pm_debug" },
{}
};
static struct platform_driver sec_pm_debug_driver = {
.driver = {
.name = "sec_pm_debug",
.of_match_table = sec_pm_debug_match_table,
},
.probe = sec_pm_debug_probe,
};
static int __init sec_pm_debug_init(void)
{
return platform_driver_register(&sec_pm_debug_driver);
}
static void __exit sec_pm_debug_exit(void)
{
platform_driver_unregister(&sec_pm_debug_driver);
}
MODULE_AUTHOR("Jonghyeon Cho <jongjaaa.cho@samsung.com>");
MODULE_DESCRIPTION("A driver to debugging the feature related power");
MODULE_LICENSE("GPL");
late_initcall(sec_pm_debug_init);
module_exit(sec_pm_debug_exit);