Files
android_kernel_samsung_sm8750/drivers/soc/qcom/qcom_stats.c
2025-08-12 22:16:57 +02:00

1692 lines
43 KiB
C
Executable File

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2011-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/cdev.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/soc/qcom/qcom_aoss.h>
#include <linux/soc/qcom/smem.h>
#include <soc/qcom/qcom_stats.h>
#include <clocksource/arm_arch_timer.h>
#if IS_ENABLED(CONFIG_SEC_PM)
#include <trace/events/power.h>
#define MAX_BUF_LEN 512
#define MSM_ARCH_TIMER_FREQ 19200000
#define GET_SEC(A) ((A) / (MSM_ARCH_TIMER_FREQ))
#define GET_MSEC(A) (((A) / (MSM_ARCH_TIMER_FREQ / 1000)) % 1000)
#define SOC_NUM_RECORDS 5
u64 last_accumulated[SOC_NUM_RECORDS];
char buf[MAX_BUF_LEN];
#endif /* CONFIG_SEC_PM */
#define RPM_DYNAMIC_ADDR 0x14
#define RPM_DYNAMIC_ADDR_MASK 0xFFFF
#define STAT_TYPE_OFFSET 0x0
#define COUNT_OFFSET 0x4
#define LAST_ENTERED_AT_OFFSET 0x8
#define LAST_EXITED_AT_OFFSET 0x10
#define ACCUMULATED_OFFSET 0x18
#define CLIENT_VOTES_OFFSET 0x20
#define DDR_STATS_MAGIC_KEY 0xA1157A75
#define DDR_STATS_MAX_NUM_MODES 0x14
#define MAX_DRV 28
#define MAX_MSG_LEN 64
#define DRV_ABSENT 0xdeaddead
#define DRV_INVALID 0xffffdead
#define VOTE_MASK 0x3fff
#define VOTE_X_SHIFT 14
#define DDR_STATS_MAGIC_KEY_ADDR 0x0
#define DDR_STATS_NUM_MODES_ADDR 0x4
#define DDR_STATS_ENTRY_ADDR 0x8
#define DDR_STATS_NAME_ADDR 0x0
#define DDR_STATS_COUNT_ADDR 0x4
#define DDR_STATS_DURATION_ADDR 0x8
#define MAX_ISLAND_STATS_NAME_LENGTH 16
#define MAX_ISLAND_STATS 6
#define ISLAND_STATS_PID 2 /* ADSP PID */
#define ISLAND_STATS_SMEM_ID 653
#define LLC_ISLAND_STATS_SMEM_ID 661
#define STATS_BASEMINOR 0
#define STATS_MAX_MINOR 1
#define STATS_DEVICE_NAME "stats"
#define SUBSYSTEM_STATS_MAGIC_NUM (0x9d)
#define SUBSYSTEM_STATS_OTHERS_NUM (-2)
#define APSS_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 0, \
struct sleep_stats *)
#define MODEM_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 1, \
struct sleep_stats *)
#define WPSS_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 2, \
struct sleep_stats *)
#define ADSP_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 3, \
struct sleep_stats *)
#define ADSP_ISLAND_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 4, \
struct sleep_stats *)
#define CDSP_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 5, \
struct sleep_stats *)
#define SLPI_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 6, \
struct sleep_stats *)
#define GPU_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 7, \
struct sleep_stats *)
#define DISPLAY_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 8, \
struct sleep_stats *)
#define SLPI_ISLAND_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 9, \
struct sleep_stats *)
#define AOSD_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 10, \
struct sleep_stats *)
#define CXSD_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 11, \
struct sleep_stats *)
#define DDR_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 12, \
struct sleep_stats *)
#define DDR_STATS_IOCTL _IOR(SUBSYSTEM_STATS_MAGIC_NUM, 13, \
struct sleep_stats *)
struct subsystem_data {
const char *name;
u32 smem_item;
u32 pid;
bool not_present;
};
static struct subsystem_data subsystems[] = {
{ "modem", 605, 1 },
{ "wpss", 605, 13 },
{ "adsp", 606, 2 },
{ "cdsp", 607, 5 },
{ "cdsp1", 607, 12 },
{ "gpdsp0", 607, 17 },
{ "gpdsp1", 607, 18 },
{ "slpi", 608, 3 },
{ "gpu", 609, 0 },
{ "display", 610, 0 },
{ "adsp_island", 613, 2 },
{ "slpi_island", 613, 3 },
{ "apss", 631, QCOM_SMEM_HOST_ANY },
};
struct stats_config {
size_t stats_offset;
size_t ddr_stats_offset;
size_t cx_vote_offset;
size_t num_records;
bool appended_stats_avail;
bool dynamic_offset;
bool subsystem_stats_in_smem;
bool read_ddr_votes;
bool read_ddr_his;
bool read_cx_final_vote;
bool ddr_freq_update;
bool island_stats_avail;
bool llc_island_stats_avail;
};
struct stats_data {
bool appended_stats_avail;
void __iomem *base;
};
struct stats_drvdata {
void __iomem *base;
const struct stats_config *config;
struct stats_data *d;
struct dentry *root;
dev_t dev_no;
struct class *stats_class;
struct device *stats_device;
struct cdev stats_cdev;
struct mutex lock;
struct qmp *qmp;
ktime_t ddr_freqsync_msg_time;
};
static struct stats_drvdata *drv;
struct sleep_stats {
u32 stat_type;
u32 count;
u64 last_entered_at;
u64 last_exited_at;
u64 accumulated;
};
struct appended_stats {
u32 client_votes;
u32 reserved[3];
};
struct island_stats {
char name[MAX_ISLAND_STATS_NAME_LENGTH];
u32 count;
u64 last_entered_at;
u64 last_exited_at;
u64 accumulated;
u32 vid;
u32 task_id;
u32 reserved[3];
};
#if IS_ENABLED(CONFIG_SEC_PM)
#define MAX_SLEEP_STATS_COUNT 10
#define MAX_SLEEP_STATS_NAME 16
static char sys_names[MAX_SLEEP_STATS_COUNT][MAX_SLEEP_STATS_NAME];
static int max_subsys_count;
static int subsys_idx[MAX_SLEEP_STATS_COUNT];
#endif
static bool subsystem_stats_debug_on;
/* Subsystem stats before and after suspend */
static struct sleep_stats *b_subsystem_stats;
static struct sleep_stats *a_subsystem_stats;
/* System sleep stats before and after suspend */
static struct sleep_stats *b_system_stats;
static struct sleep_stats *a_system_stats;
static DEFINE_MUTEX(sleep_stats_mutex);
#define DSP_SLEEP_DEBUG_ON
#if defined(DSP_SLEEP_DEBUG_ON)
#include <linux/samsung/debug/sec_debug.h>
#include <linux/workqueue.h>
#include <linux/remoteproc.h>
#define MAX_COUNT 10
struct _dsp_entry {
char name[4];
uint64_t entry_sec;
uint64_t entry_msec;
uint64_t prev_exit_sec;
uint64_t prev_exit_msec;
uint64_t error_count;
struct timespec64 interval;
int (*ssr)(void);
} DSP_ENTRY[1]; // 0 : CDSP, 1 : ADSP - adsp is disabled for the time being.
struct cdsp_loader_private {
void *pil_h;
struct kobject *boot_cdsp_obj;
struct attribute_group *attr_group;
};
static struct cdsp_loader_private *priv = NULL;
static struct device_node *cdsp_node = NULL;
#endif
static inline void get_sleep_stat_name(u32 type, char *stat_type)
{
int i;
for (i = 0; i < sizeof(u32); i++) {
stat_type[i] = type & 0xff;
type = type >> 8;
}
strim(stat_type);
}
bool has_system_slept(bool *debug_aoss)
{
int i;
bool sleep_flag = true;
char stat_type[sizeof(u32) + 1] = {0};
bool aosd_entered = false, cxsd_entered = false;
for (i = 0; i < drv->config->num_records; i++) {
get_sleep_stat_name(b_system_stats[i].stat_type, stat_type);
if (b_system_stats[i].count == a_system_stats[i].count) {
pr_info("System %s has not entered sleep\n", stat_type);
sleep_flag = false;
continue;
}
if (!strcmp(stat_type, "cxsd"))
cxsd_entered = true;
if (!strcmp(stat_type, "aosd"))
aosd_entered = true;
}
if (cxsd_entered && !aosd_entered)
*debug_aoss = true;
return sleep_flag;
}
EXPORT_SYMBOL_GPL(has_system_slept);
bool has_subsystem_slept(void)
{
int i;
bool sleep_flag = true;
for (i = 0; i < ARRAY_SIZE(subsystems); i++) {
if (subsystems[i].not_present)
continue;
if ((b_subsystem_stats[i].count == a_subsystem_stats[i].count) &&
(a_subsystem_stats[i].last_exited_at >
a_subsystem_stats[i].last_entered_at)) {
pr_info("Subsystem %s has not entered sleep\n", subsystems[i].name);
sleep_flag = false;
}
}
return sleep_flag;
}
EXPORT_SYMBOL_GPL(has_subsystem_slept);
void subsystem_sleep_debug_enable(bool enable)
{
subsystem_stats_debug_on = enable;
}
EXPORT_SYMBOL_GPL(subsystem_sleep_debug_enable);
static inline int qcom_stats_copy_to_user(unsigned long arg, struct sleep_stats *stats,
unsigned long size)
{
return copy_to_user((void __user *)arg, stats, size);
}
#if defined(DSP_SLEEP_DEBUG_ON)
extern bool dump_enabled(void);
extern void set_dump_enabled(int val);
void cdsp_restart(struct work_struct *work)
{
int prev_dump_collection = 0;
pr_err("%s start", __func__);
if (!priv)
return;
prev_dump_collection = dump_enabled();
set_dump_enabled(0);
phandle rproc_phandle;
int sz = 0;
sz = of_property_read_u32(cdsp_node, "qcom,rproc-handle", &rproc_phandle);
priv->pil_h = rproc_get_by_phandle(rproc_phandle);
pr_debug("%s: going to call rpoc_shutdown for cdsp\n", __func__);
rproc_shutdown(priv->pil_h);
msleep(800);
pr_debug("%s: going to call rproc_boot for cdsp\n", __func__);
rproc_boot(priv->pil_h);
set_dump_enabled(prev_dump_collection);
pr_err("%s end", __func__);
}
static DECLARE_WORK(dsp_ssr, cdsp_restart);
#endif
static inline void qcom_stats_update_accumulated_duration(struct sleep_stats *stats)
{
/*
* If a subsystem 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 (stats->last_entered_at > stats->last_exited_at)
stats->accumulated += (__arch_counter_get_cntvct() - stats->last_entered_at);
}
static inline void qcom_stats_copy(struct sleep_stats *src, struct sleep_stats *dst)
{
dst->stat_type = src->stat_type;
dst->count = src->count;
dst->last_entered_at = src->last_entered_at;
dst->last_exited_at = src->last_exited_at;
dst->accumulated = src->accumulated;
}
static bool ddr_stats_is_freq_overtime(struct sleep_stats *data)
{
if ((data->count == 0) && (drv->config->ddr_freq_update))
return true;
return false;
}
static u64 qcom_stats_fill_ddr_stats(void __iomem *reg, struct sleep_stats *data, u32 *entry_count)
{
u64 accumulated_duration = 0;
int i;
*entry_count = readl_relaxed(reg + DDR_STATS_NUM_MODES_ADDR);
if (*entry_count > DDR_STATS_MAX_NUM_MODES) {
pr_err("Invalid entry count\n");
return 0;
}
reg += DDR_STATS_ENTRY_ADDR;
for (i = 0; i < *entry_count; i++) {
data[i].count = readl_relaxed(reg + DDR_STATS_COUNT_ADDR);
if ((i >= 0x4) && (ddr_stats_is_freq_overtime(&data[i]))) {
pr_err("ddr_stats: Freq update failed\n");
return 0;
}
data[i].stat_type = readl_relaxed(reg + DDR_STATS_NAME_ADDR);
data[i].last_entered_at = 0xDEADDEAD;
data[i].last_exited_at = 0xDEADDEAD;
data[i].accumulated = readq_relaxed(reg + DDR_STATS_DURATION_ADDR);
accumulated_duration += data[i].accumulated;
reg += sizeof(struct sleep_stats) - 2 * sizeof(u64);
}
return accumulated_duration;
}
static int qcom_stats_device_open(struct inode *inode, struct file *file)
{
struct stats_drvdata *drv = NULL;
if (!inode || !inode->i_cdev || !file)
return -EINVAL;
drv = container_of(inode->i_cdev, struct stats_drvdata, stats_cdev);
file->private_data = drv;
return 0;
}
int qcom_stats_ddr_freqsync_msg(void)
{
static const char buf[MAX_MSG_LEN] = "{class: ddr, action: freqsync}";
int ret = 0;
if (!drv || !drv->qmp || !drv->config->read_ddr_votes)
return -ENODEV;
mutex_lock(&drv->lock);
ret = qmp_send(drv->qmp, buf, sizeof(buf));
if (ret) {
pr_err("Error sending qmp message: %d\n", ret);
mutex_unlock(&drv->lock);
return ret;
}
mutex_unlock(&drv->lock);
drv->ddr_freqsync_msg_time = ktime_get_boottime();
return ret;
}
EXPORT_SYMBOL_GPL(qcom_stats_ddr_freqsync_msg);
static int qcom_stats_ddr_freq_sync(int *modes, struct sleep_stats *stat)
{
void __iomem *reg = NULL;
u32 entry_count, name;
ktime_t now;
int i, j, ret;
if (drv->config->read_ddr_votes) {
ret = qcom_stats_ddr_freqsync_msg();
if (ret)
return ret;
now = ktime_get_boottime();
while (now < drv->ddr_freqsync_msg_time) {
udelay(500);
now = ktime_get_boottime();
}
}
reg = drv->base + drv->config->ddr_stats_offset;
qcom_stats_fill_ddr_stats(reg, stat, &entry_count);
if (drv->config->read_ddr_votes) {
for (i = 0, j = 0; i < entry_count; i++) {
name = (stat[i].stat_type >> 8) & 0xFF;
if (name == 0x1 && !stat[i].count)
break;
++j;
}
if (j < DDR_STATS_MAX_NUM_MODES)
*modes = j;
}
return 0;
}
static long qcom_stats_device_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct stats_drvdata *drv = file->private_data;
const struct subsystem_data *subsystem = NULL;
struct sleep_stats *stat;
struct sleep_stats *temp;
void __iomem *reg = NULL;
unsigned long size = sizeof(struct sleep_stats);
u32 stats_id;
int ret;
mutex_lock(&sleep_stats_mutex);
if (cmd != DDR_STATS_IOCTL)
stat = kzalloc(sizeof(struct sleep_stats), GFP_KERNEL);
else
stat = kcalloc(DDR_STATS_MAX_NUM_MODES, sizeof(struct sleep_stats), GFP_KERNEL);
if (!stat) {
mutex_unlock(&sleep_stats_mutex);
return -ENOMEM;
}
switch (cmd) {
case MODEM_IOCTL:
subsystem = &subsystems[0];
break;
case WPSS_IOCTL:
subsystem = &subsystems[1];
break;
case ADSP_IOCTL:
subsystem = &subsystems[2];
break;
case CDSP_IOCTL:
subsystem = &subsystems[3];
break;
case SLPI_IOCTL:
subsystem = &subsystems[4];
break;
case GPU_IOCTL:
subsystem = &subsystems[5];
break;
case DISPLAY_IOCTL:
subsystem = &subsystems[6];
break;
case ADSP_ISLAND_IOCTL:
subsystem = &subsystems[7];
break;
case SLPI_ISLAND_IOCTL:
subsystem = &subsystems[8];
break;
case APSS_IOCTL:
subsystem = &subsystems[9];
break;
case AOSD_IOCTL:
stats_id = 0;
if (drv->config->num_records > stats_id)
reg = drv->d[stats_id].base;
break;
case CXSD_IOCTL:
stats_id = 1;
if (drv->config->num_records > stats_id)
reg = drv->d[stats_id].base;
break;
case DDR_IOCTL:
stats_id = 2;
if (drv->config->num_records > stats_id)
reg = drv->d[stats_id].base;
break;
case DDR_STATS_IOCTL:
break;
default:
pr_err("Incorrect command error\n");
ret = -EINVAL;
goto exit;
}
if (subsystem) {
/* Items are allocated lazily, so lookup pointer each time */
temp = qcom_smem_get(subsystem->pid, subsystem->smem_item, NULL);
if (IS_ERR(temp)) {
ret = -EIO;
goto exit;
}
qcom_stats_copy(temp, stat);
qcom_stats_update_accumulated_duration(stat);
ret = qcom_stats_copy_to_user(arg, stat, size);
} else if (reg) {
memcpy_fromio(stat, reg, sizeof(*stat));
qcom_stats_update_accumulated_duration(stat);
ret = qcom_stats_copy_to_user(arg, stat, size);
} else {
int modes = DDR_STATS_MAX_NUM_MODES;
ret = qcom_stats_ddr_freq_sync(&modes, stat);
if (ret)
goto exit;
ret = qcom_stats_copy_to_user(arg, stat, modes * size);
}
exit:
kfree(stat);
mutex_unlock(&sleep_stats_mutex);
return ret;
}
static const struct file_operations qcom_stats_device_fops = {
.owner = THIS_MODULE,
.open = qcom_stats_device_open,
.unlocked_ioctl = qcom_stats_device_ioctl,
};
int ddr_stats_get_freq_count(void)
{
u32 entry_count, name;
u32 freq_count = 0;
void __iomem *reg;
int i;
if (!drv || !drv->qmp || !drv->config->read_ddr_votes)
return -ENODEV;
reg = drv->base + drv->config->ddr_stats_offset;
entry_count = readl_relaxed(reg + DDR_STATS_NUM_MODES_ADDR);
if (entry_count > DDR_STATS_MAX_NUM_MODES) {
pr_err("Invalid entry count\n");
return 0;
}
reg += DDR_STATS_ENTRY_ADDR;
for (i = 0; i < entry_count; i++) {
name = readl_relaxed(reg + DDR_STATS_NAME_ADDR);
name = (name >> 8) & 0xFF;
if (name == 0x1)
freq_count++;
reg += sizeof(struct sleep_stats) - 2 * sizeof(u64);
}
return freq_count;
}
EXPORT_SYMBOL_GPL(ddr_stats_get_freq_count);
int ddr_stats_get_residency(int freq_count, struct ddr_freq_residency *data)
{
struct sleep_stats stat[DDR_STATS_MAX_NUM_MODES];
void __iomem *reg;
u32 name, entry_count;
ktime_t now;
int i, j;
if (freq_count < 0 || !data)
return -EINVAL;
if (!drv || !drv->qmp || !drv->config->read_ddr_votes)
return -ENODEV;
now = ktime_get_boottime();
while (now < drv->ddr_freqsync_msg_time) {
udelay(500);
now = ktime_get_boottime();
}
mutex_lock(&drv->lock);
reg = drv->base + drv->config->ddr_stats_offset;
qcom_stats_fill_ddr_stats(reg, stat, &entry_count);
for (i = 0, j = 0; i < entry_count; i++) {
name = stat[i].stat_type;
if (((name >> 8) & 0xFF) == 0x1 && stat[i].count) {
data[j].freq = name >> 16;
data[j].residency = stat[i].accumulated;
if (++j > freq_count)
break;
}
}
mutex_unlock(&drv->lock);
return j;
}
EXPORT_SYMBOL_GPL(ddr_stats_get_residency);
int ddr_stats_get_ss_count(void)
{
return drv->config->read_ddr_votes ? MAX_DRV : -EOPNOTSUPP;
}
EXPORT_SYMBOL_GPL(ddr_stats_get_ss_count);
static void __iomem *qcom_stats_get_ddr_stats_data_addr(void)
{
void __iomem *reg = NULL;
u32 vote_offset;
u32 entry_count;
reg = drv->base + drv->config->ddr_stats_offset;
entry_count = readl_relaxed(reg + DDR_STATS_NUM_MODES_ADDR);
if (entry_count > DDR_STATS_MAX_NUM_MODES) {
pr_err("Invalid entry count\n");
return NULL;
}
vote_offset = DDR_STATS_ENTRY_ADDR;
vote_offset += entry_count * (sizeof(struct sleep_stats) - 2 * sizeof(u64));
reg = drv->base + drv->config->ddr_stats_offset + vote_offset;
return reg;
}
int ddr_stats_get_ss_vote_info(int ss_count,
struct ddr_stats_ss_vote_info *vote_info)
{
static const char buf[MAX_MSG_LEN] = "{class: ddr, res: drvs_ddr_votes}";
u32 val[MAX_DRV];
void __iomem *reg;
int ret, i;
if (!vote_info || !(ss_count == MAX_DRV) || !drv)
return -ENODEV;
if (!drv->qmp)
return -EOPNOTSUPP;
mutex_lock(&drv->lock);
ret = qmp_send(drv->qmp, buf, sizeof(buf));
if (ret) {
pr_err("Error sending qmp message: %d\n", ret);
mutex_unlock(&drv->lock);
return ret;
}
reg = qcom_stats_get_ddr_stats_data_addr();
if (!reg) {
pr_err("Error getting ddr stats data addr\n");
mutex_unlock(&drv->lock);
return -EINVAL;
}
for (i = 0; i < ss_count; i++, reg += sizeof(u32)) {
val[i] = readl_relaxed(reg);
if (val[i] == DRV_ABSENT) {
vote_info[i].ab = DRV_ABSENT;
vote_info[i].ib = DRV_ABSENT;
continue;
} else if (val[i] == DRV_INVALID) {
vote_info[i].ab = DRV_INVALID;
vote_info[i].ib = DRV_INVALID;
continue;
}
vote_info[i].ab = (val[i] >> VOTE_X_SHIFT) & VOTE_MASK;
vote_info[i].ib = val[i] & VOTE_MASK;
}
mutex_unlock(&drv->lock);
return 0;
}
EXPORT_SYMBOL_GPL(ddr_stats_get_ss_vote_info);
static void cxvt_info_fill_data(void __iomem *reg, u32 entry_count,
u32 *data)
{
int i;
for (i = 0; i < entry_count; i++) {
data[i] = readl_relaxed(reg);
reg += sizeof(u32);
}
}
int cx_stats_get_ss_vote_info(int ss_count,
struct qcom_stats_cx_vote_info *vote_info)
{
static const char buf[MAX_MSG_LEN] = "{class: misc_debug, res: cx_vote}";
void __iomem *reg;
int ret;
int i, j;
u32 data[((MAX_DRV + 0x3) & (~0x3))/4 + 1];
u32 entry_count = 0;
if (!vote_info || !(ss_count == MAX_DRV) || !drv)
return -ENODEV;
if (!drv->qmp || !drv->config->cx_vote_offset)
return -EOPNOTSUPP;
mutex_lock(&drv->lock);
ret = qmp_send(drv->qmp, buf, sizeof(buf));
if (ret) {
pr_err("Error sending qmp message: %d\n", ret);
mutex_unlock(&drv->lock);
return ret;
}
reg = qcom_stats_get_ddr_stats_data_addr();
if (!reg) {
pr_err("Error getting ddr stats data addr\n");
mutex_unlock(&drv->lock);
return -EINVAL;
}
if (drv->config->read_cx_final_vote)
entry_count = ((MAX_DRV + 0x3) & (~0x3))/4 + 1;
else
entry_count = ((MAX_DRV + 0x3) & (~0x3))/4;
cxvt_info_fill_data(reg, entry_count, data);
for (i = 0, j = 0; i < ((MAX_DRV + 0x3) & (~0x3))/4; i++, j += 4) {
vote_info[j].level = (data[i] & 0xff);
vote_info[j+1].level = ((data[i] & 0xff00) >> 8);
vote_info[j+2].level = ((data[i] & 0xff0000) >> 16);
vote_info[j+3].level = ((data[i] & 0xff000000) >> 24);
}
if (drv->config->read_cx_final_vote)
vote_info[j].level = (u8)data[i];
mutex_unlock(&drv->lock);
return 0;
}
EXPORT_SYMBOL_GPL(cx_stats_get_ss_vote_info);
int ddr_stats_get_change_his(struct ddr_stats_change_his_info *ddr_his_info)
{
static const char buf[MAX_MSG_LEN] = "{class: misc_debug, res: ddr_his}";
int ret;
void __iomem *reg;
if (!drv || !drv->qmp || !drv->config->read_ddr_his)
return -ENODEV;
mutex_lock(&drv->lock);
ret = qmp_send(drv->qmp, buf, sizeof(buf));
if (ret) {
pr_err("Error sending qmp message: %d\n", ret);
mutex_unlock(&drv->lock);
return ret;
}
reg = qcom_stats_get_ddr_stats_data_addr();
if (!reg) {
pr_err("Error getting ddr stats data addr\n");
mutex_unlock(&drv->lock);
return -EINVAL;
}
memcpy_fromio(ddr_his_info, reg, sizeof(struct ddr_stats_change_his_info));
mutex_unlock(&drv->lock);
return 0;
}
EXPORT_SYMBOL_GPL(ddr_stats_get_change_his);
int llc_stats_get_active_scids(struct llc_island_stats_active_scids *llc_active_scids)
{
static struct llc_island_stats_active_scids *local_scids;
if (!drv || !drv->config->llc_island_stats_avail)
return -ENODEV;
mutex_lock(&drv->lock);
if (IS_ERR_OR_NULL(local_scids)) {
local_scids = qcom_smem_get(
ISLAND_STATS_PID,
LLC_ISLAND_STATS_SMEM_ID,
NULL);
if (IS_ERR_OR_NULL(local_scids)) {
mutex_unlock(&drv->lock);
return -ENOMEM;
}
} else {
memcpy_fromio(llc_active_scids, local_scids, sizeof(*local_scids));
}
mutex_unlock(&drv->lock);
return 0;
}
EXPORT_SYMBOL_GPL(llc_stats_get_active_scids);
static void qcom_print_stats(struct seq_file *s, const struct sleep_stats *stat)
{
u64 accumulated = stat->accumulated;
/*
* If a subsystem is in sleep when reading the sleep stats adjust
* the accumulated sleep duration to show actual sleep time.
*/
if (stat->last_entered_at > stat->last_exited_at)
accumulated += arch_timer_read_counter() - stat->last_entered_at;
seq_printf(s, "Count: %u\n", stat->count);
seq_printf(s, "Last Entered At: %llu\n", stat->last_entered_at);
seq_printf(s, "Last Exited At: %llu\n", stat->last_exited_at);
seq_printf(s, "Accumulated Duration: %llu\n", accumulated);
}
static int qcom_subsystem_sleep_stats_show(struct seq_file *s, void *unused)
{
struct subsystem_data *subsystem = s->private;
struct sleep_stats *stat;
/* Items are allocated lazily, so lookup pointer each time */
stat = qcom_smem_get(subsystem->pid, subsystem->smem_item, NULL);
if (IS_ERR(stat))
return 0;
qcom_print_stats(s, stat);
return 0;
}
static int qcom_soc_sleep_stats_show(struct seq_file *s, void *unused)
{
struct stats_data *d = s->private;
void __iomem *reg = d->base;
struct sleep_stats stat;
memcpy_fromio(&stat, reg, sizeof(stat));
qcom_print_stats(s, &stat);
if (d->appended_stats_avail) {
struct appended_stats votes;
memcpy_fromio(&votes, reg + CLIENT_VOTES_OFFSET, sizeof(votes));
seq_printf(s, "Client Votes: %#x\n", votes.client_votes);
}
return 0;
}
static void print_ddr_stats(struct seq_file *s, int *count,
struct sleep_stats *data, u64 accumulated_duration)
{
u32 cp_idx = 0;
u32 name, duration = 0;
if (accumulated_duration)
duration = (data->accumulated * 100) / accumulated_duration;
name = (data->stat_type >> 8) & 0xFF;
if (name == 0x0) {
name = (data->stat_type) & 0xFF;
*count = *count + 1;
seq_printf(s,
"LPM %d:\tName:0x%x\tcount:%u\tDuration (ticks):%llu (~%u%%)\n",
*count, name, data->count, data->accumulated, duration);
} else if (name == 0x1) {
cp_idx = data->stat_type & 0x1F;
name = data->stat_type >> 16;
if (!name || !data->count)
return;
seq_printf(s,
"Freq %uMhz:\tCP IDX:%u\tcount:%u\tDuration (ticks):%llu (~%u%%)\n",
name, cp_idx, data->count, data->accumulated, duration);
}
}
static int ddr_stats_show(struct seq_file *s, void *d)
{
struct sleep_stats data[DDR_STATS_MAX_NUM_MODES];
void __iomem *reg = s->private;
u32 entry_count;
u64 accumulated_duration = 0, accumulated_duration_ddr_mode = 0;
int i, lpm_count = 0;
accumulated_duration = qcom_stats_fill_ddr_stats(reg, data, &entry_count);
for (i = 0; i < DDR_STATS_NUM_MODES_ADDR; i++)
accumulated_duration_ddr_mode += data[i].accumulated;
for (i = 0; i < DDR_STATS_NUM_MODES_ADDR; i++)
print_ddr_stats(s, &lpm_count, &data[i], accumulated_duration_ddr_mode);
if (!accumulated_duration) {
seq_puts(s, "ddr_stats: Freq update failed.\n");
return 0;
}
accumulated_duration -= accumulated_duration_ddr_mode;
for (i = DDR_STATS_NUM_MODES_ADDR; i < entry_count; i++)
print_ddr_stats(s, &lpm_count, &data[i], accumulated_duration);
return 0;
}
static int island_stats_show(struct seq_file *s, void *unused)
{
struct island_stats *stat;
int i;
/* Items are allocated lazily, so lookup pointer each time */
stat = qcom_smem_get(ISLAND_STATS_PID, ISLAND_STATS_SMEM_ID, NULL);
if (IS_ERR(stat))
return 0;
for (i = 0; i < MAX_ISLAND_STATS; i++) {
if (!strcmp(stat[i].name, "DEADDEAD"))
continue;
seq_printf(s, "Name: %s\n", stat[i].name);
seq_printf(s, "Count: %u\n", stat[i].count);
seq_printf(s, "Last Entered At: %llu\n", stat[i].last_entered_at);
seq_printf(s, "Last Exited At: %llu\n", stat[i].last_exited_at);
seq_printf(s, "Accumulated Duration: %llu\n", stat[i].accumulated);
seq_printf(s, "Vid: %u\n", stat[i].vid);
seq_printf(s, "task_id: %u\n", stat[i].task_id);
}
return 0;
}
static int vote_info_show(struct seq_file *s, void *d)
{
int i = 0;
struct ddr_stats_ss_vote_info ddr_vote_info[MAX_DRV];
struct qcom_stats_cx_vote_info cx_vote_info[MAX_DRV];
ddr_stats_get_ss_vote_info(MAX_DRV, ddr_vote_info);
cx_stats_get_ss_vote_info(MAX_DRV, cx_vote_info);
for (i = 0; i < MAX_DRV; i++) {
pr_info("PM: vote info: drv%d:\tcx(%d),\tddr(%d,\t%d)\n",
i, cx_vote_info[i].level, ddr_vote_info[i].ab, ddr_vote_info[i].ib);
seq_printf(s, "drv%d:\tcx(%d),\tddr(%d,\t%d)\n",
i, cx_vote_info[i].level, ddr_vote_info[i].ab, ddr_vote_info[i].ib);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(qcom_soc_sleep_stats);
DEFINE_SHOW_ATTRIBUTE(qcom_subsystem_sleep_stats);
DEFINE_SHOW_ATTRIBUTE(ddr_stats);
DEFINE_SHOW_ATTRIBUTE(island_stats);
DEFINE_SHOW_ATTRIBUTE(vote_info);
static int qcom_create_stats_device(struct stats_drvdata *drv)
{
int ret;
ret = alloc_chrdev_region(&drv->dev_no, STATS_BASEMINOR, STATS_MAX_MINOR,
STATS_DEVICE_NAME);
if (ret)
return ret;
cdev_init(&drv->stats_cdev, &qcom_stats_device_fops);
ret = cdev_add(&drv->stats_cdev, drv->dev_no, 1);
if (ret) {
unregister_chrdev_region(drv->dev_no, 1);
return ret;
}
drv->stats_class = class_create(STATS_DEVICE_NAME);
if (IS_ERR_OR_NULL(drv->stats_class)) {
cdev_del(&drv->stats_cdev);
unregister_chrdev_region(drv->dev_no, 1);
return PTR_ERR(drv->stats_class);
}
drv->stats_device = device_create(drv->stats_class, NULL, drv->dev_no, NULL,
STATS_DEVICE_NAME);
if (IS_ERR_OR_NULL(drv->stats_device)) {
class_destroy(drv->stats_class);
cdev_del(&drv->stats_cdev);
unregister_chrdev_region(drv->dev_no, 1);
return PTR_ERR(drv->stats_device);
}
return ret;
}
static void qcom_create_island_stat_files(struct dentry *root, void __iomem *reg,
struct stats_data *d,
const struct stats_config *config)
{
if (!config->island_stats_avail)
return;
debugfs_create_file("island_stats", 0400, root, NULL, &island_stats_fops);
}
static void qcom_create_vote_info_files(struct dentry *root, void __iomem *reg,
struct stats_data *d,
const struct stats_config *config)
{
debugfs_create_file("vote_info", 0400, root, NULL, &vote_info_fops);
}
static void qcom_create_ddr_stat_files(struct dentry *root, void __iomem *reg,
struct stats_data *d,
const struct stats_config *config)
{
size_t stats_offset;
u32 key;
if (!config->ddr_stats_offset)
return;
stats_offset = config->ddr_stats_offset;
key = readl_relaxed(reg + stats_offset + DDR_STATS_MAGIC_KEY_ADDR);
if (key == DDR_STATS_MAGIC_KEY)
debugfs_create_file("ddr_stats", 0400, root, reg + stats_offset, &ddr_stats_fops);
}
static void qcom_create_soc_sleep_stat_files(struct dentry *root, void __iomem *reg,
struct stats_data *d,
const struct stats_config *config)
{
char stat_type[sizeof(u32) + 1] = {0};
size_t stats_offset = config->stats_offset;
u32 offset = 0, type;
int i;
/*
* On RPM targets, stats offset location is dynamic and changes from target
* to target and sometimes from build to build for same target.
*
* In such cases the dynamic address is present at 0x14 offset from base
* address in devicetree. The last 16bits indicates the stats_offset.
*/
if (config->dynamic_offset) {
stats_offset = readl(reg + RPM_DYNAMIC_ADDR);
stats_offset &= RPM_DYNAMIC_ADDR_MASK;
}
for (i = 0; i < config->num_records; i++) {
d[i].base = reg + offset + stats_offset;
/*
* Read the low power mode name and create debugfs file for it.
* The names read could be of below,
* (may change depending on low power mode supported).
* For rpmh-sleep-stats: "aosd", "cxsd" and "ddr".
* For rpm-sleep-stats: "vmin" and "vlow".
*/
type = readl(d[i].base);
get_sleep_stat_name(type, stat_type);
debugfs_create_file(stat_type, 0400, root, &d[i],
&qcom_soc_sleep_stats_fops);
#if IS_ENABLED(CONFIG_SEC_PM)
/* Store each system's name */
strcpy(sys_names[i], stat_type);
#endif
offset += sizeof(struct sleep_stats);
if (d[i].appended_stats_avail)
offset += sizeof(struct appended_stats);
}
}
static void qcom_create_subsystem_stat_files(struct dentry *root,
const struct stats_config *config,
struct device_node *node)
{
int i, j, n_subsystems;
const char *name;
if (!config->subsystem_stats_in_smem)
return;
n_subsystems = of_property_count_strings(node, "ss-name");
if (n_subsystems < 0)
return;
for (i = 0; i < n_subsystems; i++) {
of_property_read_string_index(node, "ss-name", i, &name);
for (j = 0; j < ARRAY_SIZE(subsystems); j++) {
if (!strcmp(subsystems[j].name, name)) {
debugfs_create_file(subsystems[j].name, 0400, root,
(void *)&subsystems[j],
&qcom_subsystem_sleep_stats_fops);
#if IS_ENABLED(CONFIG_SEC_PM)
/* Store each subsystem's idx */
subsys_idx[max_subsys_count++] = j;
#endif
break;
}
}
}
}
#if IS_ENABLED(CONFIG_SEC_PM)
static char *print_soc_stats(char *buf_ptr, const char *annotation)
{
size_t stats_offset = drv->config->stats_offset;
struct sleep_stats stat;
char stat_type[sizeof(u32) + 1] = {0};
unsigned int duration_sec, duration_msec;
u64 accumulated;
u32 offset = 0, type;
bool is_exit = (!strcmp("exit", annotation)) ? true : false;
int i, j;
if (drv->config->dynamic_offset) {
stats_offset = readl(drv->base + RPM_DYNAMIC_ADDR);
stats_offset &= RPM_DYNAMIC_ADDR_MASK;
}
buf_ptr += sprintf(buf_ptr, "PM: %s: ", annotation);
for (i = 0; i < drv->config->num_records; i++) {
/* Get soc_stat's name */
drv->d[i].base = drv->base + offset + stats_offset;
type = readl(drv->d[i].base);
for (j = 0; j < sizeof(u32); j++) {
stat_type[j] = type & 0xff;
type = type >> 8;
}
strim(stat_type);
/* Get soc_stat's sleep info */
memcpy_fromio(&stat, drv->d[i].base, sizeof(stat));
accumulated = stat.accumulated;
if (stat.last_entered_at > stat.last_exited_at)
accumulated += arch_timer_read_counter()
- stat.last_entered_at;
/* Check non-sleep issue */
if (is_exit && accumulated == last_accumulated[i])
buf_ptr += sprintf(buf_ptr, "*");
last_accumulated[i] = accumulated;
/* Calculate accumulated duration */
duration_sec = GET_SEC(accumulated);
duration_msec = GET_MSEC(accumulated);
buf_ptr += sprintf(buf_ptr, "%s(%d, %u.%u), ",
stat_type,
stat.count,
duration_sec, duration_msec);
/* Move to next soc_stat */
offset += sizeof(struct sleep_stats);
if (drv->d[i].appended_stats_avail)
offset += sizeof(struct appended_stats);
}
buf_ptr += sprintf(buf_ptr, "\n");
return buf_ptr;
}
static char *print_subsystem_stats(char *buf_ptr, const char *annotation)
{
struct subsystem_data *subsystem;
struct sleep_stats *stat;
unsigned int duration_sec, duration_msec;
u64 accumulated;
int idx, i;
#if defined(DSP_SLEEP_DEBUG_ON)
struct _dsp_entry *dsp_entry = NULL;
int is_debug_low = 0;
unsigned int debug_level = 0;
bool is_exit = (!strcmp("exit", annotation)) ? true : false;
#endif
buf_ptr += sprintf(buf_ptr, "PM: %s: ", annotation);
for (i = 0; i < max_subsys_count; i++) {
idx = subsys_idx[i];
/* Get each subsystem's info */
subsystem = &subsystems[idx];
stat = qcom_smem_get(subsystem->pid, subsystem->smem_item, NULL);
if (IS_ERR(stat)) {
pr_err("%s: Failed to get qcom_smem for %s, ret=%ld\n", __func__,
subsystems[idx].name, PTR_ERR(stat));
/* Even though getting info from smem is failed, next subsystem should be checked */
continue;
}
/* Calculate accumulated duration */
accumulated = stat->accumulated;
if (stat->last_entered_at > stat->last_exited_at)
accumulated += arch_timer_read_counter()
- stat->last_entered_at;
duration_sec = GET_SEC(accumulated);
duration_msec = GET_MSEC(accumulated);
#if defined(DSP_SLEEP_DEBUG_ON)
dsp_entry = (!strcmp(subsystem->name, "cdsp")) ? &DSP_ENTRY[0] : NULL;
if (dsp_entry != NULL) {
if (!is_exit) {
// entry
dsp_entry->entry_sec = duration_sec;
dsp_entry->entry_msec = duration_msec;
} else {
//exit
/* Error detected if exit duration is same as entry */
if((duration_sec == dsp_entry->entry_sec &&
duration_msec == dsp_entry->entry_msec) &&
(duration_sec == dsp_entry->prev_exit_sec &&
duration_msec == dsp_entry->prev_exit_msec)) {
struct timespec64 curr_kts = ktime_to_timespec64(ktime_get_boottime());
if (dsp_entry->interval.tv_sec != 0) {
time64_t diff_kts = curr_kts.tv_sec - dsp_entry->interval.tv_sec;
if (diff_kts > 60) { // don't update error count within 1 min
dsp_entry->error_count++;
printk("entry error cnt : %llu\n", dsp_entry->error_count);
dsp_entry->interval = ktime_to_timespec64(ktime_get_boottime());
}
} else {
dsp_entry->interval = ktime_to_timespec64(ktime_get_boottime());
}
} else {
dsp_entry->error_count = 0;
}
dsp_entry->prev_exit_sec = duration_sec;
dsp_entry->prev_exit_msec = duration_msec;
}
}
#endif
buf_ptr += sprintf(buf_ptr, "%s(%d, %u.%u), ",
subsystem->name,
stat->count,
duration_sec, duration_msec);
}
#if defined(DSP_SLEEP_DEBUG_ON)
// 0 : CDSP, 1 : ADSP
for (i = 0; i < sizeof(DSP_ENTRY) / sizeof(struct _dsp_entry); i++) {
dsp_entry = &DSP_ENTRY[i];
if(dsp_entry->error_count > MAX_COUNT) {
debug_level = sec_debug_level();
switch (debug_level) {
case SEC_DEBUG_LEVEL_LOW:
is_debug_low = 1;
break;
case SEC_DEBUG_LEVEL_MID:
is_debug_low = 0;
break;
}
if (!is_debug_low) {
pr_err("entry error cnt : %llu\n", dsp_entry->error_count);
pr_err("Intentional crash for %s\n", dsp_entry->name);
BUG_ON(1);
} else {
dsp_entry->error_count = 0;
pr_err("reset entry error cnt : %llu\n", dsp_entry->error_count);
pr_err("Intentional cdsp subsystem restart\n");
schedule_work(&dsp_ssr);
}
}
}
#endif
return buf_ptr;
}
static void sec_sleep_stats_show(const char *annotation)
{
char *buf_ptr = buf;
buf_ptr = print_soc_stats(buf_ptr, annotation);
buf_ptr = print_subsystem_stats(buf_ptr, annotation);
buf_ptr -= 2;
buf_ptr += sprintf(buf_ptr, "\n");
pr_info("%s", buf);
}
static void qcom_stats_debug_suspend_trace_probe(void *unused,
const char *action, int val, bool start)
{
/*
* SUSPEND
* start(1), val(1), action(machine_suspend)
*/
if (start && val > 0 && !strcmp("machine_suspend", action))
sec_sleep_stats_show("entry");
/*
* RESUME
*start(0), val(1), action(machine_suspend)
*/
if (!start && val > 0 && !strcmp("machine_suspend", action))
sec_sleep_stats_show("exit");
}
#endif
static int qcom_stats_probe(struct platform_device *pdev)
{
void __iomem *reg;
struct dentry *root;
const struct stats_config *config;
struct stats_data *d;
int i;
int ret;
#if defined(DSP_SLEEP_DEBUG_ON)
priv = NULL;
cdsp_node = NULL;
#endif
#if IS_ENABLED(CONFIG_SEC_PM)
/* Register callback for cheking subsystem stats */
ret = register_trace_suspend_resume(
qcom_stats_debug_suspend_trace_probe, NULL);
if (ret) {
pr_err("%s: Failed to register suspend trace callback, ret=%d\n",
__func__, ret);
}
#endif
config = device_get_match_data(&pdev->dev);
if (!config)
return -ENODEV;
reg = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(reg))
return -ENOMEM;
d = devm_kcalloc(&pdev->dev, config->num_records,
sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
return -ENOMEM;
for (i = 0; i < config->num_records; i++)
d[i].appended_stats_avail = config->appended_stats_avail;
root = debugfs_create_dir("qcom_stats", NULL);
qcom_create_subsystem_stat_files(root, config, pdev->dev.of_node);
qcom_create_soc_sleep_stat_files(root, reg, d, config);
qcom_create_ddr_stat_files(root, reg, d, config);
qcom_create_island_stat_files(root, reg, d, config);
qcom_create_vote_info_files(root, reg, d, config);
drv->d = d;
drv->config = config;
drv->base = reg;
drv->root = root;
drv->ddr_freqsync_msg_time = 0;
mutex_init(&drv->lock);
ret = qcom_create_stats_device(drv);
if (ret)
goto fail_create_stats_device;
if (config->read_ddr_votes && config->ddr_stats_offset) {
drv->qmp = qmp_get(&pdev->dev);
if (IS_ERR(drv->qmp)) {
ret = PTR_ERR(drv->qmp);
goto fail;
}
}
subsystem_stats_debug_on = false;
b_subsystem_stats = devm_kcalloc(&pdev->dev, ARRAY_SIZE(subsystems),
sizeof(struct sleep_stats), GFP_KERNEL);
if (!b_subsystem_stats) {
ret = -ENOMEM;
goto fail;
}
a_subsystem_stats = devm_kcalloc(&pdev->dev, ARRAY_SIZE(subsystems),
sizeof(struct sleep_stats), GFP_KERNEL);
if (!a_subsystem_stats) {
ret = -ENOMEM;
goto fail;
}
b_system_stats = devm_kcalloc(&pdev->dev, drv->config->num_records,
sizeof(struct sleep_stats), GFP_KERNEL);
if (!b_system_stats) {
ret = -ENOMEM;
goto fail;
}
a_system_stats = devm_kcalloc(&pdev->dev, drv->config->num_records,
sizeof(struct sleep_stats), GFP_KERNEL);
if (!a_system_stats) {
ret = -ENOMEM;
goto fail;
}
platform_set_drvdata(pdev, drv);
#if defined(DSP_SLEEP_DEBUG_ON)
priv = platform_get_drvdata(pdev);
cdsp_node = pdev->dev.of_node;
#endif
return 0;
fail:
device_destroy(drv->stats_class, drv->dev_no);
class_destroy(drv->stats_class);
cdev_del(&drv->stats_cdev);
unregister_chrdev_region(drv->dev_no, 1);
fail_create_stats_device:
debugfs_remove_recursive(drv->root);
return ret;
}
static int qcom_stats_remove(struct platform_device *pdev)
{
struct stats_drvdata *drv = platform_get_drvdata(pdev);
device_destroy(drv->stats_class, drv->dev_no);
class_destroy(drv->stats_class);
cdev_del(&drv->stats_cdev);
unregister_chrdev_region(drv->dev_no, 1);
debugfs_remove_recursive(drv->root);
#if IS_ENABLED(CONFIG_SEC_PM)
unregister_trace_suspend_resume(
qcom_stats_debug_suspend_trace_probe, NULL);
#endif
return 0;
}
static int qcom_stats_suspend(struct device *dev)
{
struct stats_drvdata *drv = dev_get_drvdata(dev);
struct sleep_stats *tmp;
void __iomem *reg = NULL;
int i;
u32 stats_id = 0;
if (!subsystem_stats_debug_on)
return 0;
mutex_lock(&sleep_stats_mutex);
for (i = 0; i < ARRAY_SIZE(subsystems); i++) {
tmp = qcom_smem_get(subsystems[i].pid, subsystems[i].smem_item, NULL);
if (IS_ERR(tmp)) {
subsystems[i].not_present = true;
continue;
} else
subsystems[i].not_present = false;
qcom_stats_copy(tmp, b_subsystem_stats + i);
}
for (i = 0; i < drv->config->num_records; i++, stats_id++) {
if (drv->config->num_records > stats_id)
reg = drv->d[stats_id].base;
if (reg)
memcpy_fromio(b_system_stats + i, reg, sizeof(struct sleep_stats));
}
mutex_unlock(&sleep_stats_mutex);
return 0;
}
static int qcom_stats_resume(struct device *dev)
{
struct stats_drvdata *drv = dev_get_drvdata(dev);
struct sleep_stats *tmp;
void __iomem *reg = NULL;
int i;
u32 stats_id = 0;
if (!subsystem_stats_debug_on)
return 0;
mutex_lock(&sleep_stats_mutex);
for (i = 0; i < ARRAY_SIZE(subsystems); i++) {
if (subsystems[i].not_present)
continue;
tmp = qcom_smem_get(subsystems[i].pid, subsystems[i].smem_item, NULL);
if (IS_ERR(tmp))
continue;
qcom_stats_copy(tmp, a_subsystem_stats + i);
}
for (i = 0; i < drv->config->num_records; i++, stats_id++) {
if (drv->config->num_records > stats_id)
reg = drv->d[stats_id].base;
if (reg)
memcpy_fromio(a_system_stats + i, reg, sizeof(struct sleep_stats));
}
mutex_unlock(&sleep_stats_mutex);
return 0;
}
static const struct stats_config rpm_data = {
.stats_offset = 0,
.num_records = 2,
.appended_stats_avail = true,
.dynamic_offset = true,
.subsystem_stats_in_smem = false,
};
/* Older RPM firmwares have the stats at a fixed offset instead */
static const struct stats_config rpm_data_dba0 = {
.stats_offset = 0xdba0,
.num_records = 2,
.appended_stats_avail = true,
.dynamic_offset = false,
.subsystem_stats_in_smem = false,
};
static const struct stats_config rpmh_data_sdm845 = {
.stats_offset = 0x48,
.num_records = 2,
.appended_stats_avail = false,
.dynamic_offset = false,
.subsystem_stats_in_smem = true,
};
static const struct stats_config rpmh_data = {
.stats_offset = 0x48,
.ddr_stats_offset = 0xb8,
.num_records = 3,
.appended_stats_avail = false,
.dynamic_offset = false,
.subsystem_stats_in_smem = true,
};
static const struct stats_config rpmh_v2_data = {
.stats_offset = 0x48,
.ddr_stats_offset = 0xb8,
.cx_vote_offset = 0xb8,
.num_records = 3,
.appended_stats_avail = false,
.dynamic_offset = false,
.subsystem_stats_in_smem = true,
.read_ddr_votes = true,
};
static const struct stats_config rpmh_v3_data = {
.stats_offset = 0x48,
.ddr_stats_offset = 0xb8,
.cx_vote_offset = 0xb8,
.num_records = 3,
.appended_stats_avail = false,
.dynamic_offset = false,
.subsystem_stats_in_smem = true,
.read_ddr_votes = true,
.ddr_freq_update = true,
};
static const struct stats_config rpmh_v4_data = {
.stats_offset = 0x48,
.ddr_stats_offset = 0xb8,
.cx_vote_offset = 0xb8,
.num_records = 3,
.appended_stats_avail = false,
.dynamic_offset = false,
.subsystem_stats_in_smem = true,
.read_ddr_votes = true,
.read_ddr_his = true,
.ddr_freq_update = true,
.read_cx_final_vote = true,
.island_stats_avail = true,
.llc_island_stats_avail = true,
};
static const struct of_device_id qcom_stats_table[] = {
{ .compatible = "qcom,apq8084-rpm-stats", .data = &rpm_data_dba0 },
{ .compatible = "qcom,msm8226-rpm-stats", .data = &rpm_data_dba0 },
{ .compatible = "qcom,msm8916-rpm-stats", .data = &rpm_data_dba0 },
{ .compatible = "qcom,msm8974-rpm-stats", .data = &rpm_data_dba0 },
{ .compatible = "qcom,rpm-stats", .data = &rpm_data },
{ .compatible = "qcom,rpmh-stats", .data = &rpmh_data },
{ .compatible = "qcom,rpmh-stats-v2", .data = &rpmh_v2_data },
{ .compatible = "qcom,rpmh-stats-v3", .data = &rpmh_v3_data },
{ .compatible = "qcom,rpmh-stats-v4", .data = &rpmh_v4_data },
{ .compatible = "qcom,sdm845-rpmh-stats", .data = &rpmh_data_sdm845 },
{ }
};
MODULE_DEVICE_TABLE(of, qcom_stats_table);
static const struct dev_pm_ops qcom_stats_pm_ops = {
.suspend_late = qcom_stats_suspend,
.resume_early = qcom_stats_resume,
};
static struct platform_driver qcom_stats = {
.probe = qcom_stats_probe,
.remove = qcom_stats_remove,
.driver = {
.name = "qcom_stats",
.of_match_table = qcom_stats_table,
.pm = &qcom_stats_pm_ops,
},
};
static int __init qcom_stats_init(void)
{
return platform_driver_register(&qcom_stats);
}
late_initcall(qcom_stats_init);
static void __exit qcom_stats_exit(void)
{
platform_driver_unregister(&qcom_stats);
}
module_exit(qcom_stats_exit)
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. (QTI) Stats driver");
MODULE_LICENSE("GPL v2");