mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 03:27:03 +02:00
Merge "soc: qcom: Add cpuss sleep stats driver snapshot"
This commit is contained in:
commit
fe94365a51
|
|
@ -42,6 +42,15 @@ config QCOM_CPR
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called qcom-cpr
|
||||
|
||||
config QCOM_CPUSS_SLEEP_STATS
|
||||
tristate "Qualcomm Technologies, Inc. (QTI) CPUSS sleep stats driver"
|
||||
depends on DEBUG_FS
|
||||
help
|
||||
Qualcomm Technologies, Inc. (QTI) CPUSS sleep stats driver to get
|
||||
hardware LPM counts and residency for particular core/cluster
|
||||
low power modes. This driver creates debugfs entry which provide
|
||||
provision to read those counters.
|
||||
|
||||
config QCOM_GENI_SE
|
||||
tristate "QCOM GENI Serial Engine Driver"
|
||||
depends on ARCH_QCOM || COMPILE_TEST
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ obj-$(CONFIG_QCOM_AOSS_QMP) += qcom_aoss.o
|
|||
obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o
|
||||
obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
|
||||
obj-$(CONFIG_QCOM_CPR) += cpr.o
|
||||
obj-$(CONFIG_QCOM_CPUSS_SLEEP_STATS) += qcom_cpuss_sleep_stats.o
|
||||
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
|
||||
obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o
|
||||
obj-$(CONFIG_QCOM_OCMEM) += ocmem.o
|
||||
|
|
|
|||
746
drivers/soc/qcom/qcom_cpuss_sleep_stats.c
Normal file
746
drivers/soc/qcom/qcom_cpuss_sleep_stats.c
Normal file
|
|
@ -0,0 +1,746 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/qcom_scm.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define MAX_POSSIBLE_CPUS 8
|
||||
#define MIN_POSSIBLE_CPUS 1
|
||||
#define SEQ_LPM_STR_SZ 22
|
||||
#define CPUNAME_SZ 6
|
||||
#define STATS_NAME_SZ 25
|
||||
#define OFFSET_8BYTES 0x08
|
||||
#define OFFSET_4BYTES 0x04
|
||||
|
||||
#define MAX_CPU_LPM_BITS 4
|
||||
#define MAX_CL_LPM_BITS 4
|
||||
#define MAX_CPU_RESIDENCY_BITS 5
|
||||
#define MAX_CL_RESIDENCY_BITS 3
|
||||
|
||||
enum {
|
||||
APSS_LPM_COUNTER_CPUx_C1_LO_VAL,
|
||||
APSS_LPM_COUNTER_CPUx_C2D_LO_VAL,
|
||||
APSS_LPM_COUNTER_CPUx_C3_LO_VAL,
|
||||
APSS_LPM_COUNTER_CPUx_C4_LO_VAL,
|
||||
APSS_CPU_LPM_RESIDENCY_CNTR_CFG_n,
|
||||
APSS_CL_LPM_RESIDENCY_CNTR_CFG,
|
||||
APSS_LPM_RESIDENCY_C2_D2_CNTR_n,
|
||||
APSS_LPM_RESIDENCY_C3_CNTR_n,
|
||||
APSS_LPM_RESIDENCY_C4_D4_CNTR_n,
|
||||
};
|
||||
|
||||
static u32 qcom_cpuss_cntr_v1_offsets[] = {
|
||||
[APSS_LPM_COUNTER_CPUx_C1_LO_VAL] = 0x8000,
|
||||
[APSS_LPM_COUNTER_CPUx_C2D_LO_VAL] = 0x8048,
|
||||
[APSS_LPM_COUNTER_CPUx_C3_LO_VAL] = 0x8090,
|
||||
[APSS_LPM_COUNTER_CPUx_C4_LO_VAL] = 0x80D8,
|
||||
[APSS_CPU_LPM_RESIDENCY_CNTR_CFG_n] = 0xC004,
|
||||
[APSS_CL_LPM_RESIDENCY_CNTR_CFG] = 0xC030,
|
||||
[APSS_LPM_RESIDENCY_C2_D2_CNTR_n] = 0xC040,
|
||||
[APSS_LPM_RESIDENCY_C3_CNTR_n] = 0xC090,
|
||||
[APSS_LPM_RESIDENCY_C4_D4_CNTR_n] = 0xC0D0,
|
||||
};
|
||||
|
||||
static u32 qcom_cpuss_cntr_v2_offsets[] = {
|
||||
[APSS_LPM_COUNTER_CPUx_C1_LO_VAL] = 0x8000,
|
||||
[APSS_LPM_COUNTER_CPUx_C2D_LO_VAL] = 0x8028,
|
||||
[APSS_LPM_COUNTER_CPUx_C3_LO_VAL] = 0x8050,
|
||||
[APSS_LPM_COUNTER_CPUx_C4_LO_VAL] = 0x8078,
|
||||
[APSS_CPU_LPM_RESIDENCY_CNTR_CFG_n] = 0xC004,
|
||||
[APSS_CL_LPM_RESIDENCY_CNTR_CFG] = 0xC030,
|
||||
[APSS_LPM_RESIDENCY_C2_D2_CNTR_n] = 0xC040,
|
||||
[APSS_LPM_RESIDENCY_C3_CNTR_n] = 0xC090,
|
||||
[APSS_LPM_RESIDENCY_C4_D4_CNTR_n] = 0xC0D0,
|
||||
};
|
||||
|
||||
|
||||
/* bits are per CPU LPM_CFG register */
|
||||
#define LPM_COUNTER_EN_C1 BIT(0)
|
||||
#define LPM_COUNTER_EN_C2D BIT(1)
|
||||
#define LPM_COUNTER_EN_C3 BIT(2)
|
||||
#define LPM_COUNTER_EN_C4 BIT(3)
|
||||
#define LPM_COUNTER_RESET_C1 BIT(4)
|
||||
#define LPM_COUNTER_RESET_C2D BIT(5)
|
||||
#define LPM_COUNTER_RESET_C3 BIT(6)
|
||||
#define LPM_COUNTER_RESET_C4 BIT(7)
|
||||
|
||||
#define RESET_ALL_CPU_LPM (LPM_COUNTER_RESET_C1 | \
|
||||
LPM_COUNTER_RESET_C2D | LPM_COUNTER_RESET_C3 | \
|
||||
LPM_COUNTER_RESET_C4)
|
||||
|
||||
/* bits are per CL LPM_CFG register */
|
||||
#define LPM_COUNTER_EN_D1 BIT(0)
|
||||
#define LPM_COUNTER_EN_D2D BIT(1)
|
||||
#define LPM_COUNTER_EN_D3 BIT(2)
|
||||
#define LPM_COUNTER_EN_D4 BIT(3)
|
||||
#define LPM_COUNTER_RESET_D1 BIT(4)
|
||||
#define LPM_COUNTER_RESET_D2D BIT(5)
|
||||
#define LPM_COUNTER_RESET_D3 BIT(6)
|
||||
#define LPM_COUNTER_RESET_D4 BIT(7)
|
||||
|
||||
|
||||
#define RESET_ALL_CL_LPM (LPM_COUNTER_RESET_D1 | \
|
||||
LPM_COUNTER_RESET_D2D | LPM_COUNTER_RESET_D3 | \
|
||||
LPM_COUNTER_RESET_D4)
|
||||
|
||||
/* Core residency cfg as per register */
|
||||
#define RESIDENCY_CNTR_C2_EN BIT(0)
|
||||
#define RESIDENCY_CNTR_C2_CLR BIT(1)
|
||||
#define RESIDENCY_CNTR_C3_EN BIT(2)
|
||||
#define RESIDENCY_CNTR_C3_CLR BIT(3)
|
||||
#define RESIDENCY_CNTR_C4_EN BIT(4)
|
||||
#define RESIDENCY_CNTR_C4_CLR BIT(5)
|
||||
|
||||
#define CLR_ALL_CPU_RESIDENCY (RESIDENCY_CNTR_C2_CLR | \
|
||||
RESIDENCY_CNTR_C3_CLR | RESIDENCY_CNTR_C4_CLR)
|
||||
|
||||
/* Cluster residency bits as per cfg register*/
|
||||
#define RESIDENCY_CNTR_D2_EN BIT(0)
|
||||
#define RESIDENCY_CNTR_D2_CLR BIT(1)
|
||||
#define RESIDENCY_CNTR_D4_EN BIT(2)
|
||||
#define RESIDENCY_CNTR_D4_CLR BIT(3)
|
||||
|
||||
#define CLR_CL_RESIDENCY (RESIDENCY_CNTR_D2_CLR | \
|
||||
RESIDENCY_CNTR_D4_CLR)
|
||||
|
||||
struct qcom_cpuss_stats {
|
||||
char mode_name[20];
|
||||
void __iomem *reg; /* iomapped reg */
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
struct qcom_target_info {
|
||||
struct platform_device *pdev;
|
||||
int ncpu;
|
||||
phys_addr_t per_cpu_lpm_cfg[MAX_POSSIBLE_CPUS];
|
||||
u32 per_cpu_lpm_cfg_size[MAX_POSSIBLE_CPUS];
|
||||
phys_addr_t apss_seq_mem_base;
|
||||
u32 apss_seq_mem_size;
|
||||
phys_addr_t l3_seq_lpm_cfg;
|
||||
u32 l3_seq_lpm_size;
|
||||
u32 *offsets;
|
||||
struct qcom_cpuss_stats complete_stats;
|
||||
|
||||
struct dentry *stats_rootdir;
|
||||
struct dentry *cpu_dir[MAX_POSSIBLE_CPUS];
|
||||
struct dentry *cl_rootdir;
|
||||
struct mutex stats_reset_lock;
|
||||
};
|
||||
|
||||
static char *get_str_cpu_lpm_state(u8 state)
|
||||
{
|
||||
switch (state) {
|
||||
case LPM_COUNTER_EN_C1:
|
||||
return "C1_count";
|
||||
case LPM_COUNTER_EN_C2D:
|
||||
return "C2D_count";
|
||||
case LPM_COUNTER_EN_C3:
|
||||
return "C3_count";
|
||||
case LPM_COUNTER_EN_C4:
|
||||
return "C4_count";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char *get_str_cl_lpm_state(u8 state)
|
||||
{
|
||||
switch (state) {
|
||||
case LPM_COUNTER_EN_D1:
|
||||
return "D1_count";
|
||||
case LPM_COUNTER_EN_D2D:
|
||||
return "D2D_count";
|
||||
case LPM_COUNTER_EN_D3:
|
||||
return "D3_count";
|
||||
case LPM_COUNTER_EN_D4:
|
||||
return "D4_count";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char *get_str_cpu_res(u32 cfg)
|
||||
{
|
||||
switch (cfg) {
|
||||
case RESIDENCY_CNTR_C2_EN:
|
||||
return "C2_residency";
|
||||
case RESIDENCY_CNTR_C3_EN:
|
||||
return "C3_residency";
|
||||
case RESIDENCY_CNTR_C4_EN:
|
||||
return "C4_residency";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static char *get_str_cl_res(u8 cfg)
|
||||
{
|
||||
switch (cfg) {
|
||||
case RESIDENCY_CNTR_D2_EN:
|
||||
return "D2_residency";
|
||||
case RESIDENCY_CNTR_D4_EN:
|
||||
return "D4_residency";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static int get_cpu_lpm_read_offset(u8 cpu, u8 cpu_mode, u32 *regs)
|
||||
{
|
||||
int offset;
|
||||
|
||||
switch (cpu_mode) {
|
||||
case LPM_COUNTER_EN_C1:
|
||||
offset = regs[APSS_LPM_COUNTER_CPUx_C1_LO_VAL] + (cpu * OFFSET_8BYTES);
|
||||
break;
|
||||
case LPM_COUNTER_EN_C2D:
|
||||
offset = regs[APSS_LPM_COUNTER_CPUx_C2D_LO_VAL] + (cpu * OFFSET_8BYTES);
|
||||
break;
|
||||
case LPM_COUNTER_EN_C3:
|
||||
offset = regs[APSS_LPM_COUNTER_CPUx_C3_LO_VAL] + (cpu * OFFSET_8BYTES);
|
||||
break;
|
||||
case LPM_COUNTER_EN_C4:
|
||||
offset = regs[APSS_LPM_COUNTER_CPUx_C4_LO_VAL] + (cpu * OFFSET_8BYTES);
|
||||
break;
|
||||
default:
|
||||
pr_err("Unknown mode\n");
|
||||
offset = -EINVAL;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int get_cpu_residency_read_offset(u8 cpu, u8 cpu_res_cfg, u32 *regs)
|
||||
{
|
||||
int offset;
|
||||
|
||||
switch (cpu_res_cfg) {
|
||||
case RESIDENCY_CNTR_C2_EN:
|
||||
offset = regs[APSS_LPM_RESIDENCY_C2_D2_CNTR_n] + (cpu * OFFSET_8BYTES);
|
||||
break;
|
||||
case RESIDENCY_CNTR_C3_EN:
|
||||
offset = regs[APSS_LPM_RESIDENCY_C3_CNTR_n] + (cpu * OFFSET_8BYTES);
|
||||
break;
|
||||
case RESIDENCY_CNTR_C4_EN:
|
||||
offset = regs[APSS_LPM_RESIDENCY_C4_D4_CNTR_n] + (cpu * OFFSET_8BYTES);
|
||||
break;
|
||||
default:
|
||||
pr_err("Unknown mode\n");
|
||||
offset = -EINVAL;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int get_cl_residency_read_offset(int ncpu, u8 cl_res_cfg, u32 *regs)
|
||||
{
|
||||
int offset;
|
||||
|
||||
switch (cl_res_cfg) {
|
||||
case RESIDENCY_CNTR_D2_EN:
|
||||
offset = regs[APSS_LPM_RESIDENCY_C2_D2_CNTR_n] + OFFSET_8BYTES * ncpu;
|
||||
break;
|
||||
case RESIDENCY_CNTR_D4_EN:
|
||||
offset = regs[APSS_LPM_RESIDENCY_C4_D4_CNTR_n] + OFFSET_8BYTES * ncpu;
|
||||
break;
|
||||
default:
|
||||
pr_err("Unknown mode\n");
|
||||
offset = -EINVAL;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int get_cl_lpm_read_offset(int ncpu, u8 cl_mode, u32 *regs)
|
||||
{
|
||||
int offset;
|
||||
|
||||
switch (cl_mode) {
|
||||
case LPM_COUNTER_EN_D1:
|
||||
offset = regs[APSS_LPM_COUNTER_CPUx_C1_LO_VAL] + ncpu * OFFSET_8BYTES;
|
||||
break;
|
||||
case LPM_COUNTER_EN_D2D:
|
||||
offset = regs[APSS_LPM_COUNTER_CPUx_C2D_LO_VAL] + ncpu * OFFSET_8BYTES;
|
||||
break;
|
||||
case LPM_COUNTER_EN_D3:
|
||||
offset = regs[APSS_LPM_COUNTER_CPUx_C3_LO_VAL] + ncpu * OFFSET_8BYTES;
|
||||
break;
|
||||
case LPM_COUNTER_EN_D4:
|
||||
offset = regs[APSS_LPM_COUNTER_CPUx_C4_LO_VAL] + ncpu * OFFSET_8BYTES;
|
||||
break;
|
||||
default:
|
||||
pr_err("Unknown mode\n");
|
||||
offset = -EINVAL;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int qcom_cpuss_sleep_stats_show(struct seq_file *s, void *d)
|
||||
{
|
||||
void __iomem *reg = (void __iomem *)s->private;
|
||||
u64 val;
|
||||
|
||||
val = readq_relaxed(reg);
|
||||
seq_printf(s, "%ld\n", val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(qcom_cpuss_sleep_stats);
|
||||
|
||||
static int qcom_cpuss_all_stats_show(struct seq_file *s, void *d)
|
||||
{
|
||||
struct list_head *node1 = (struct list_head *)s->private;
|
||||
struct qcom_cpuss_stats *data;
|
||||
u64 count;
|
||||
|
||||
list_for_each_entry(data, node1, node) {
|
||||
count = readq_relaxed(data->reg);
|
||||
seq_printf(s, "%s: %ld\n", data->mode_name, count);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(qcom_cpuss_all_stats);
|
||||
|
||||
static ssize_t qcom_cpuss_reset_clear_lpm_residency(phys_addr_t addr, u32 new_val)
|
||||
{
|
||||
u32 val;
|
||||
int ret = 0;
|
||||
|
||||
ret = qcom_scm_io_readl(addr, &val);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
val |= new_val;
|
||||
ret = qcom_scm_io_writel(addr, val);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
/* clear reset */
|
||||
val &= ~new_val;
|
||||
ret = qcom_scm_io_writel(addr, val);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
pr_err("Cannot reset lpm hw count and residency\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static bool check_val(const char __user *in, size_t count)
|
||||
{
|
||||
loff_t ppos = 0;
|
||||
char buffer[2] = {0};
|
||||
int ret;
|
||||
|
||||
ret = simple_write_to_buffer(buffer, sizeof(buffer) - 1,
|
||||
&ppos, in, count - 1);
|
||||
if (ret > 0)
|
||||
return strcmp(buffer, "1") ? false : true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static ssize_t qcom_cpuss_stats_reset_write(struct file *file,
|
||||
const char __user *buffer, size_t count, loff_t *off)
|
||||
{
|
||||
struct inode *in = file->f_inode;
|
||||
struct qcom_target_info *t_info;
|
||||
int ncpu, i;
|
||||
ssize_t ret;
|
||||
struct platform_device *pdev;
|
||||
|
||||
t_info = (struct qcom_target_info *)in->i_private;
|
||||
ncpu = t_info->ncpu;
|
||||
pdev = t_info->pdev;
|
||||
|
||||
if (!check_val(buffer, count))
|
||||
return -EINVAL;
|
||||
|
||||
/* Reset cpu LPM/Residencies */
|
||||
mutex_lock(&t_info->stats_reset_lock);
|
||||
for (i = 0; i < ncpu; i++) {
|
||||
ret = qcom_cpuss_reset_clear_lpm_residency(t_info->per_cpu_lpm_cfg[i],
|
||||
RESET_ALL_CPU_LPM);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
ret = qcom_cpuss_reset_clear_lpm_residency(t_info->apss_seq_mem_base +
|
||||
t_info->offsets[APSS_CPU_LPM_RESIDENCY_CNTR_CFG_n] + 0x4 * i,
|
||||
CLR_ALL_CPU_RESIDENCY);
|
||||
if (ret)
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Reset cluster LPM/Residencies */
|
||||
ret = qcom_cpuss_reset_clear_lpm_residency(t_info->l3_seq_lpm_cfg,
|
||||
RESET_ALL_CL_LPM);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
ret = qcom_cpuss_reset_clear_lpm_residency(t_info->apss_seq_mem_base +
|
||||
t_info->offsets[APSS_CL_LPM_RESIDENCY_CNTR_CFG], CLR_CL_RESIDENCY);
|
||||
if (ret)
|
||||
goto error;
|
||||
mutex_unlock(&t_info->stats_reset_lock);
|
||||
|
||||
return count;
|
||||
|
||||
error:
|
||||
mutex_unlock(&t_info->stats_reset_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations qcom_cpuss_stats_reset_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.write = qcom_cpuss_stats_reset_write,
|
||||
};
|
||||
|
||||
static int store_stats_data(struct qcom_target_info *t_info, char *str,
|
||||
void __iomem *reg)
|
||||
{
|
||||
struct qcom_cpuss_stats *store_stats_data;
|
||||
|
||||
store_stats_data = devm_kzalloc(&t_info->pdev->dev, sizeof(*store_stats_data),
|
||||
GFP_KERNEL);
|
||||
if (!store_stats_data)
|
||||
return -ENOMEM;
|
||||
|
||||
store_stats_data->reg = reg;
|
||||
strscpy(store_stats_data->mode_name, str,
|
||||
sizeof(store_stats_data->mode_name));
|
||||
|
||||
list_add_tail(&store_stats_data->node, &t_info->complete_stats.node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_cpuss_sleep_stats_create_cpu_debugfs(struct qcom_target_info *t_info,
|
||||
int cpu, u32 lpm_cfg)
|
||||
{
|
||||
void __iomem *reg, *base;
|
||||
struct platform_device *pdev = t_info->pdev;
|
||||
u32 offset;
|
||||
int bit, ret;
|
||||
char cpu_name[CPUNAME_SZ] = {0}, stats_name[STATS_NAME_SZ] = {0}, *state;
|
||||
|
||||
snprintf(cpu_name, sizeof(cpu_name), "pcpu%u", cpu);
|
||||
t_info->cpu_dir[cpu] = debugfs_create_dir(cpu_name,
|
||||
t_info->stats_rootdir);
|
||||
|
||||
base = devm_ioremap(&pdev->dev, t_info->apss_seq_mem_base,
|
||||
t_info->apss_seq_mem_size);
|
||||
if (!base)
|
||||
return -ENOMEM;
|
||||
|
||||
for (bit = 0; bit < MAX_CPU_LPM_BITS; bit++) {
|
||||
if (lpm_cfg & BIT(bit)) {
|
||||
offset = get_cpu_lpm_read_offset(cpu, BIT(bit), t_info->offsets);
|
||||
if (offset == -EINVAL)
|
||||
return offset;
|
||||
|
||||
reg = base + offset;
|
||||
state = get_str_cpu_lpm_state(1 << bit);
|
||||
if (state) {
|
||||
debugfs_create_file(state, 0444,
|
||||
t_info->cpu_dir[cpu],
|
||||
(void *) reg,
|
||||
&qcom_cpuss_sleep_stats_fops);
|
||||
snprintf(stats_name, sizeof(stats_name),
|
||||
"pcpu%u: %s", cpu, state);
|
||||
ret = store_stats_data(t_info, stats_name, reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_cpuss_sleep_stats_create_cluster_debugfs(struct qcom_target_info *t_info,
|
||||
u32 cl_cfg)
|
||||
{
|
||||
void __iomem *reg, *base;
|
||||
struct platform_device *pdev = t_info->pdev;
|
||||
u32 offset;
|
||||
int bit, ret;
|
||||
char *state;
|
||||
|
||||
t_info->cl_rootdir = debugfs_create_dir("L3", t_info->stats_rootdir);
|
||||
if (!t_info->cl_rootdir)
|
||||
return -ENOMEM;
|
||||
|
||||
base = devm_ioremap(&pdev->dev, t_info->apss_seq_mem_base,
|
||||
t_info->apss_seq_mem_size);
|
||||
if (!base)
|
||||
return -ENOMEM;
|
||||
|
||||
for (bit = 0; bit < MAX_CL_LPM_BITS; bit++) {
|
||||
if (cl_cfg & BIT(bit)) {
|
||||
offset = get_cl_lpm_read_offset(t_info->ncpu, BIT(bit), t_info->offsets);
|
||||
if (offset == -EINVAL)
|
||||
return offset;
|
||||
|
||||
reg = base + offset;
|
||||
state = get_str_cl_lpm_state(1 << bit);
|
||||
if (state) {
|
||||
debugfs_create_file(state, 0444,
|
||||
t_info->cl_rootdir,
|
||||
(void *) reg,
|
||||
&qcom_cpuss_sleep_stats_fops);
|
||||
ret = store_stats_data(t_info, state, reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_cpuss_sleep_stats_create_cpu_residency_debugfs(struct qcom_target_info *t_info,
|
||||
int cpu, u32 residency_cfg)
|
||||
{
|
||||
void __iomem *reg, *base;
|
||||
struct platform_device *pdev = t_info->pdev;
|
||||
u32 offset;
|
||||
int bit, ret;
|
||||
char stats_name[STATS_NAME_SZ] = {0}, *state;
|
||||
|
||||
base = devm_ioremap(&pdev->dev, t_info->apss_seq_mem_base,
|
||||
t_info->apss_seq_mem_size);
|
||||
if (!base)
|
||||
return -ENOMEM;
|
||||
|
||||
for (bit = 0; bit < MAX_CPU_RESIDENCY_BITS; bit += 2) {
|
||||
if (residency_cfg & BIT(bit)) {
|
||||
offset = get_cpu_residency_read_offset(cpu, BIT(bit), t_info->offsets);
|
||||
if (offset == -EINVAL)
|
||||
return offset;
|
||||
|
||||
reg = base + offset;
|
||||
state = get_str_cpu_res(1 << bit);
|
||||
if (state) {
|
||||
debugfs_create_file(state, 0444,
|
||||
t_info->cpu_dir[cpu],
|
||||
(void *) reg,
|
||||
&qcom_cpuss_sleep_stats_fops);
|
||||
snprintf(stats_name, sizeof(stats_name),
|
||||
"pcpu%u: %s", cpu, state);
|
||||
ret = store_stats_data(t_info, stats_name, reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_cpuss_sleep_stats_create_cl_residency_debugfs(struct qcom_target_info *t_info,
|
||||
u32 cl_residency_cfg)
|
||||
{
|
||||
void __iomem *reg;
|
||||
struct platform_device *pdev = t_info->pdev;
|
||||
u32 offset;
|
||||
int bit, ret;
|
||||
char *state;
|
||||
|
||||
for (bit = 0; bit < MAX_CL_RESIDENCY_BITS; bit += 2) {
|
||||
if (cl_residency_cfg & BIT(bit)) {
|
||||
offset = get_cl_residency_read_offset(t_info->ncpu,
|
||||
BIT(bit), t_info->offsets);
|
||||
if (offset == -EINVAL)
|
||||
return offset;
|
||||
|
||||
reg = devm_ioremap(&pdev->dev, t_info->apss_seq_mem_base +
|
||||
offset, 0x4);
|
||||
if (!reg)
|
||||
return -ENOMEM;
|
||||
state = get_str_cl_res(1 << bit);
|
||||
if (state) {
|
||||
debugfs_create_file(state, 0444, t_info->cl_rootdir,
|
||||
(void *) reg,
|
||||
&qcom_cpuss_sleep_stats_fops);
|
||||
ret = store_stats_data(t_info, state, reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_cpuss_read_lpm_and_residency_cfg_informaion(struct qcom_target_info *t_info)
|
||||
{
|
||||
u32 val;
|
||||
int i, ret;
|
||||
phys_addr_t addr;
|
||||
|
||||
/* per cpu lpm and residency */
|
||||
for (i = 0; i < t_info->ncpu; i++) {
|
||||
addr = t_info->per_cpu_lpm_cfg[i];
|
||||
ret = qcom_scm_io_readl(addr, &val);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
ret = qcom_cpuss_sleep_stats_create_cpu_debugfs(t_info, i, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
addr = t_info->apss_seq_mem_base;
|
||||
addr += (t_info->offsets[APSS_CPU_LPM_RESIDENCY_CNTR_CFG_n] + OFFSET_4BYTES * i);
|
||||
ret = qcom_scm_io_readl(addr, &val);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
ret = qcom_cpuss_sleep_stats_create_cpu_residency_debugfs(t_info, i, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* cluster lpm */
|
||||
addr = t_info->l3_seq_lpm_cfg;
|
||||
ret = qcom_scm_io_readl(addr, &val);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
ret = qcom_cpuss_sleep_stats_create_cluster_debugfs(t_info, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* cluster residency */
|
||||
addr = t_info->apss_seq_mem_base + t_info->offsets[APSS_CL_LPM_RESIDENCY_CNTR_CFG];
|
||||
ret = qcom_scm_io_readl(addr, &val);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
ret = qcom_cpuss_sleep_stats_create_cl_residency_debugfs(t_info, val);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qcom_cpuss_sleep_stats_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret, i;
|
||||
struct dentry *root_dir;
|
||||
struct qcom_target_info *t_info;
|
||||
struct resource *res;
|
||||
|
||||
t_info = devm_kzalloc(&pdev->dev, sizeof(struct qcom_target_info),
|
||||
GFP_KERNEL);
|
||||
if (!t_info)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&t_info->complete_stats.node);
|
||||
|
||||
root_dir = debugfs_create_dir("qcom_cpuss_sleep_stats", NULL);
|
||||
t_info->stats_rootdir = root_dir;
|
||||
t_info->pdev = pdev;
|
||||
|
||||
/* Get cfg address for cpu/cluster */
|
||||
for (i = 0; i < MAX_POSSIBLE_CPUS; i++) {
|
||||
char reg_name[SEQ_LPM_STR_SZ] = {0};
|
||||
|
||||
snprintf(reg_name, sizeof(reg_name), "seq_lpm_cntr_cfg_cpu%u", i);
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
reg_name);
|
||||
if (!res)
|
||||
continue;
|
||||
|
||||
t_info->per_cpu_lpm_cfg[i] = res->start;
|
||||
t_info->per_cpu_lpm_cfg_size[i] = resource_size(res);
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"apss_seq_mem_base");
|
||||
if (!res)
|
||||
return -ENODEV;
|
||||
|
||||
t_info->apss_seq_mem_base = res->start;
|
||||
t_info->apss_seq_mem_size = resource_size(res);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
||||
"l3_seq_lpm_cntr_cfg");
|
||||
if (!res)
|
||||
return -ENODEV;
|
||||
|
||||
t_info->l3_seq_lpm_cfg = res->start;
|
||||
t_info->l3_seq_lpm_size = resource_size(res);
|
||||
|
||||
of_property_read_u32(pdev->dev.of_node, "num-cpus", &t_info->ncpu);
|
||||
if (t_info->ncpu < MIN_POSSIBLE_CPUS || t_info->ncpu > MAX_POSSIBLE_CPUS)
|
||||
return -EINVAL;
|
||||
|
||||
t_info->offsets = (u32 *)device_get_match_data(&pdev->dev);
|
||||
if (!t_info->offsets)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* Function to read cfgs register to know lpm stats per cpu/cluster and
|
||||
* create debugfs
|
||||
*/
|
||||
ret = qcom_cpuss_read_lpm_and_residency_cfg_informaion(t_info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
debugfs_create_file("stats", 0444, root_dir,
|
||||
(void *) &t_info->complete_stats.node,
|
||||
&qcom_cpuss_all_stats_fops);
|
||||
|
||||
/* Debugfs to reset all LPM and residency */
|
||||
mutex_init(&t_info->stats_reset_lock);
|
||||
debugfs_create_file("reset", 0220, root_dir, (void *) t_info,
|
||||
&qcom_cpuss_stats_reset_fops);
|
||||
|
||||
platform_set_drvdata(pdev, root_dir);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qcom_cpuss_sleep_stats_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dentry *root = platform_get_drvdata(pdev);
|
||||
|
||||
debugfs_remove_recursive(root);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_cpuss_stats_table[] = {
|
||||
{ .compatible = "qcom,cpuss-sleep-stats", .data = &qcom_cpuss_cntr_v1_offsets },
|
||||
{ .compatible = "qcom,cpuss-sleep-stats-v2", .data = &qcom_cpuss_cntr_v2_offsets },
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver qcom_cpuss_sleep_stats = {
|
||||
.probe = qcom_cpuss_sleep_stats_probe,
|
||||
.remove = qcom_cpuss_sleep_stats_remove,
|
||||
.driver = {
|
||||
.name = "qcom_cpuss_sleep_stats",
|
||||
.of_match_table = qcom_cpuss_stats_table,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(qcom_cpuss_sleep_stats);
|
||||
|
||||
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. (QTI) CPUSS sleep stats driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
Loading…
Reference in New Issue
Block a user