306 lines
7.8 KiB
C
306 lines
7.8 KiB
C
/* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/types.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/soc/qcom/smem.h>
|
|
#include <asm/arch_timer.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/sysfs.h>
|
|
#include "rpmh_master_stat.h"
|
|
|
|
#define UNIT_DIST 0x14
|
|
#define REG_VALID 0x0
|
|
#define REG_DATA_LO 0x4
|
|
#define REG_DATA_HI 0x8
|
|
|
|
#define GET_ADDR(REG, UNIT_NO) (REG + (UNIT_DIST * UNIT_NO))
|
|
|
|
enum master_smem_id {
|
|
MPSS = 605,
|
|
ADSP,
|
|
CDSP,
|
|
SLPI,
|
|
GPU,
|
|
DISPLAY,
|
|
};
|
|
|
|
enum master_pid {
|
|
PID_APSS = 0,
|
|
PID_MPSS = 1,
|
|
PID_ADSP = 2,
|
|
PID_SLPI = 3,
|
|
PID_CDSP = 5,
|
|
PID_GPU = PID_APSS,
|
|
PID_DISPLAY = PID_APSS,
|
|
};
|
|
|
|
enum profile_data {
|
|
POWER_DOWN_START,
|
|
POWER_UP_END,
|
|
POWER_DOWN_END,
|
|
POWER_UP_START,
|
|
NUM_UNIT,
|
|
};
|
|
|
|
struct msm_rpmh_master_data {
|
|
char *master_name;
|
|
enum master_smem_id smem_id;
|
|
enum master_pid pid;
|
|
};
|
|
|
|
static const struct msm_rpmh_master_data rpmh_masters[] = {
|
|
{"MPSS", MPSS, PID_MPSS},
|
|
{"ADSP", ADSP, PID_ADSP},
|
|
{"CDSP", CDSP, PID_CDSP},
|
|
{"SLPI", SLPI, PID_SLPI},
|
|
{"GPU", GPU, PID_GPU},
|
|
{"DISPLAY", DISPLAY, PID_DISPLAY},
|
|
};
|
|
|
|
struct msm_rpmh_master_stats {
|
|
uint32_t version_id;
|
|
uint32_t counts;
|
|
uint64_t last_entered;
|
|
uint64_t last_exited;
|
|
uint64_t accumulated_duration;
|
|
};
|
|
|
|
struct msm_rpmh_profile_unit {
|
|
uint64_t value;
|
|
uint64_t valid;
|
|
};
|
|
|
|
struct rpmh_master_stats_prv_data {
|
|
struct kobj_attribute ka;
|
|
struct kobject *kobj;
|
|
};
|
|
|
|
static struct msm_rpmh_master_stats apss_master_stats;
|
|
static void __iomem *rpmh_unit_base;
|
|
|
|
static DEFINE_MUTEX(rpmh_stats_mutex);
|
|
|
|
static void msm_rpmh_master_stats_print_data(struct seq_file *s,
|
|
struct msm_rpmh_master_stats *record,
|
|
const char *name)
|
|
{
|
|
uint64_t temp_accumulated_duration = record->accumulated_duration;
|
|
/*
|
|
* If a master is in sleep when reading the sleep stats from SMEM
|
|
* adjust the accumulated sleep duration to show actual sleep time.
|
|
* This ensures that the displayed stats are real when used for
|
|
* the purpose of computing battery utilization.
|
|
*/
|
|
if (record->last_entered > record->last_exited)
|
|
temp_accumulated_duration +=
|
|
(arch_counter_get_cntvct()
|
|
- record->last_entered);
|
|
|
|
seq_printf(s, "%s\n\tVersion:0x%x\n"
|
|
"\tSleep Count:0x%x\n"
|
|
"\tSleep Last Entered At:0x%llx\n"
|
|
"\tSleep Last Exited At:0x%llx\n"
|
|
"\tSleep Accumulated Duration:0x%llx\n\n",
|
|
name, record->version_id,
|
|
record->counts, record->last_entered,
|
|
record->last_exited, temp_accumulated_duration);
|
|
}
|
|
|
|
static int rpmh_master_stats_show(struct seq_file *s, void *data)
|
|
{
|
|
int i = 0;
|
|
size_t size = 0;
|
|
struct msm_rpmh_master_stats *record = NULL;
|
|
|
|
mutex_lock(&rpmh_stats_mutex);
|
|
/* First Read APSS master stats */
|
|
msm_rpmh_master_stats_print_data(s, &apss_master_stats,
|
|
"APSS");
|
|
|
|
/*
|
|
* Read SMEM data written by masters
|
|
*/
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rpmh_masters); i++) {
|
|
record = (struct msm_rpmh_master_stats *) qcom_smem_get(
|
|
rpmh_masters[i].pid,
|
|
rpmh_masters[i].smem_id, &size);
|
|
if (!IS_ERR_OR_NULL(record))
|
|
msm_rpmh_master_stats_print_data(s, record,
|
|
rpmh_masters[i].master_name);
|
|
}
|
|
|
|
mutex_unlock(&rpmh_stats_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rpmh_master_stats_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, rpmh_master_stats_show, inode->i_private);
|
|
}
|
|
|
|
static inline void msm_rpmh_apss_master_stats_update(
|
|
struct msm_rpmh_profile_unit *profile_unit)
|
|
{
|
|
apss_master_stats.counts++;
|
|
apss_master_stats.last_entered = profile_unit[POWER_DOWN_END].value;
|
|
apss_master_stats.last_exited = profile_unit[POWER_UP_START].value;
|
|
apss_master_stats.accumulated_duration +=
|
|
(apss_master_stats.last_exited
|
|
- apss_master_stats.last_entered);
|
|
}
|
|
|
|
void msm_rpmh_master_stats_update(void)
|
|
{
|
|
int i;
|
|
struct msm_rpmh_profile_unit profile_unit[NUM_UNIT];
|
|
|
|
if (!rpmh_unit_base)
|
|
return;
|
|
|
|
for (i = POWER_DOWN_END; i < NUM_UNIT; i++) {
|
|
profile_unit[i].valid = readl_relaxed(rpmh_unit_base +
|
|
GET_ADDR(REG_VALID, i));
|
|
|
|
/*
|
|
* Do not update APSS stats if valid bit is not set.
|
|
* It means APSS did not execute cx-off sequence.
|
|
* This can be due to fall through at some point.
|
|
*/
|
|
|
|
if (!(profile_unit[i].valid & BIT(REG_VALID)))
|
|
return;
|
|
|
|
profile_unit[i].value = readl_relaxed(rpmh_unit_base +
|
|
GET_ADDR(REG_DATA_LO, i));
|
|
profile_unit[i].value |= ((uint64_t)
|
|
readl_relaxed(rpmh_unit_base +
|
|
GET_ADDR(REG_DATA_HI, i)) << 32);
|
|
}
|
|
msm_rpmh_apss_master_stats_update(profile_unit);
|
|
}
|
|
EXPORT_SYMBOL(msm_rpmh_master_stats_update);
|
|
|
|
ssize_t master_stats_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
int i = 0;
|
|
size_t size = 0;
|
|
struct msm_rpmh_master_stats *record = NULL;
|
|
char stats_buf[1024];
|
|
uint64_t temp_accumulated_duration = 0;
|
|
|
|
mutex_lock(&rpmh_stats_mutex);
|
|
|
|
/* First Read APSS master stats */
|
|
|
|
temp_accumulated_duration = apss_master_stats.accumulated_duration;
|
|
|
|
if (apss_master_stats.last_entered > apss_master_stats.last_exited)
|
|
temp_accumulated_duration +=
|
|
(arch_counter_get_cntvct()
|
|
- apss_master_stats.last_entered);
|
|
|
|
snprintf(stats_buf, sizeof(stats_buf),
|
|
"APSS\n\tVersion:0x%x\n"
|
|
"\tSleep Count:0x%x\n"
|
|
"\tSleep Last Entered At:0x%llx\n"
|
|
"\tSleep Last Exited At:0x%llx\n"
|
|
"\tSleep Accumulated Duration:0x%llx\n\n",
|
|
apss_master_stats.version_id,
|
|
apss_master_stats.counts, apss_master_stats.last_entered,
|
|
apss_master_stats.last_exited, temp_accumulated_duration);
|
|
/*
|
|
* Read SMEM data written by masters
|
|
*/
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rpmh_masters); i++) {
|
|
record = (struct msm_rpmh_master_stats *) qcom_smem_get(
|
|
rpmh_masters[i].pid,
|
|
rpmh_masters[i].smem_id, &size);
|
|
if (!IS_ERR_OR_NULL(record)) {
|
|
temp_accumulated_duration = record->accumulated_duration;
|
|
|
|
if (record->last_entered > record->last_exited)
|
|
temp_accumulated_duration +=
|
|
(arch_counter_get_cntvct()
|
|
- record->last_entered);
|
|
|
|
snprintf(stats_buf + strlen(stats_buf), sizeof(stats_buf),
|
|
"%s\n\tVersion:0x%x\n"
|
|
"\tSleep Count:0x%x\n"
|
|
"\tSleep Last Entered At:0x%llx\n"
|
|
"\tSleep Last Exited At:0x%llx\n"
|
|
"\tSleep Accumulated Duration:0x%llx\n\n",
|
|
rpmh_masters[i].master_name, record->version_id,
|
|
record->counts, record->last_entered,
|
|
record->last_exited, temp_accumulated_duration);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&rpmh_stats_mutex);
|
|
|
|
return snprintf(buf, sizeof(stats_buf), "%s", stats_buf);
|
|
}
|
|
|
|
static int msm_rpmh_master_stats_probe(struct platform_device *pdev)
|
|
{
|
|
rpmh_unit_base = of_iomap(pdev->dev.of_node, 0);
|
|
if (!rpmh_unit_base) {
|
|
pr_err("Failed to get rpmh_unit_base\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
apss_master_stats.version_id = 0x1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_rpmh_master_stats_remove(struct platform_device *pdev)
|
|
{
|
|
iounmap(rpmh_unit_base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id rpmh_master_table[] = {
|
|
{.compatible = "qcom,rpmh-master-stats-v1"},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver msm_rpmh_master_stats_driver = {
|
|
.probe = msm_rpmh_master_stats_probe,
|
|
.remove = msm_rpmh_master_stats_remove,
|
|
.driver = {
|
|
.name = "msm_rpmh_master_stats",
|
|
.of_match_table = rpmh_master_table,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(msm_rpmh_master_stats_driver);
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MSM RPMH Master Statistics driver");
|
|
MODULE_ALIAS("platform:msm_rpmh_master_stat_log");
|