blob: 3a77ed8eecf89d36495ec62c43d92549e58447b2 [file] [log] [blame]
/* Copyright (c) 2017-2018, 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 <asm/arch_timer.h>
#include <soc/qcom/smem.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 ssize_t msm_rpmh_master_stats_print_data(char *prvbuf, ssize_t length,
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);
return snprintf(prvbuf, length, "%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 ssize_t msm_rpmh_master_stats_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
ssize_t length;
int i = 0;
unsigned int size = 0;
struct msm_rpmh_master_stats *record = NULL;
mutex_lock(&rpmh_stats_mutex);
/* First Read APSS master stats */
length = msm_rpmh_master_stats_print_data(buf, PAGE_SIZE,
&apss_master_stats, "APSS");
/* Read SMEM data written by other masters */
for (i = 0; i < ARRAY_SIZE(rpmh_masters); i++) {
record = (struct msm_rpmh_master_stats *) smem_get_entry(
rpmh_masters[i].smem_id, &size,
rpmh_masters[i].pid, 0);
if (!IS_ERR_OR_NULL(record) && (PAGE_SIZE - length > 0))
length += msm_rpmh_master_stats_print_data(
buf + length, PAGE_SIZE - length,
record,
rpmh_masters[i].master_name);
}
mutex_unlock(&rpmh_stats_mutex);
return length;
}
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);
static int msm_rpmh_master_stats_probe(struct platform_device *pdev)
{
struct rpmh_master_stats_prv_data *prvdata = NULL;
struct kobject *rpmh_master_stats_kobj = NULL;
int ret = -ENOMEM;
if (!pdev)
return -EINVAL;
prvdata = devm_kzalloc(&pdev->dev, sizeof(*prvdata), GFP_KERNEL);
if (!prvdata)
return ret;
rpmh_master_stats_kobj = kobject_create_and_add(
"rpmh_stats",
power_kobj);
if (!rpmh_master_stats_kobj)
return ret;
prvdata->kobj = rpmh_master_stats_kobj;
sysfs_attr_init(&prvdata->ka.attr);
prvdata->ka.attr.mode = 0444;
prvdata->ka.attr.name = "master_stats";
prvdata->ka.show = msm_rpmh_master_stats_show;
prvdata->ka.store = NULL;
ret = sysfs_create_file(prvdata->kobj, &prvdata->ka.attr);
if (ret) {
pr_err("sysfs_create_file failed\n");
goto fail_sysfs;
}
rpmh_unit_base = of_iomap(pdev->dev.of_node, 0);
if (!rpmh_unit_base) {
pr_err("Failed to get rpmh_unit_base\n");
ret = -ENOMEM;
goto fail_iomap;
}
apss_master_stats.version_id = 0x1;
platform_set_drvdata(pdev, prvdata);
return ret;
fail_iomap:
sysfs_remove_file(prvdata->kobj, &prvdata->ka.attr);
fail_sysfs:
kobject_put(prvdata->kobj);
return ret;
}
static int msm_rpmh_master_stats_remove(struct platform_device *pdev)
{
struct rpmh_master_stats_prv_data *prvdata;
if (!pdev)
return -EINVAL;
prvdata = (struct rpmh_master_stats_prv_data *)
platform_get_drvdata(pdev);
sysfs_remove_file(prvdata->kobj, &prvdata->ka.attr);
kobject_put(prvdata->kobj);
platform_set_drvdata(pdev, NULL);
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");