Print the LPM enter/exit alternative for only timestamp lines and no DRV vote present. While at this also print the timestamp shifted by time shift given. Change-Id: I03c6e45d49a6c7c0af17dc7dad4f42f8806939a6 Signed-off-by: Maulik Shah <mkshah@codeaurora.org>
311 lines
6.5 KiB
C
311 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/types.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#define MAX_QMP_MSG_SIZE 96
|
|
#define MODE_AOSS 0xaa
|
|
#define MODE_CXPC 0xcc
|
|
#define MODE_DDR 0xdd
|
|
#define MODE_STR(m) (m == MODE_CXPC ? "CXPC" : \
|
|
(m == MODE_AOSS ? "AOSS" : \
|
|
(m == MODE_DDR ? "DDR" : "")))
|
|
|
|
#define VX_MODE_MASK_TYPE 0xFF
|
|
#define VX_MODE_MASK_LOGSIZE 0xFF
|
|
#define VX_MODE_SHIFT_LOGSIZE 8
|
|
#define VX_FLAG_MASK_DUR 0xFFFF
|
|
#define VX_FLAG_MASK_TS 0xFF
|
|
#define VX_FLAG_SHIFT_TS 16
|
|
#define VX_FLAG_MASK_FLUSH_THRESH 0xFF
|
|
#define VX_FLAG_SHIFT_FLUSH_THRESH 24
|
|
|
|
#define read_word(base, itr) ({ \
|
|
u32 v; \
|
|
v = le32_to_cpu(readl_relaxed(base + itr)); \
|
|
pr_debug("Addr:%p val:%#x\n", base + itr, v); \
|
|
itr += sizeof(u32); \
|
|
/* Barrier to enssure sequential read */ \
|
|
smp_rmb(); \
|
|
v; \
|
|
})
|
|
|
|
struct vx_header {
|
|
struct {
|
|
u16 unused;
|
|
u8 logsize;
|
|
u8 type;
|
|
} mode;
|
|
struct {
|
|
u8 flush_threshold;
|
|
u8 ts_shift;
|
|
u16 dur_ms;
|
|
} flags;
|
|
};
|
|
|
|
struct vx_data {
|
|
u32 ts;
|
|
u32 *drv_vx;
|
|
};
|
|
|
|
struct vx_log {
|
|
struct vx_header header;
|
|
struct vx_data *data;
|
|
int loglines;
|
|
};
|
|
|
|
struct vx_platform_data {
|
|
void __iomem *base;
|
|
struct dentry *vx_file;
|
|
size_t ndrv;
|
|
const char **drvs;
|
|
};
|
|
|
|
static const char * const drv_names_lahaina[] = {
|
|
"TZ", "HYP", "HLOS", "L3", "SECPROC", "AUDIO", "SENSOR", "AOP",
|
|
"DEBUG", "GPU", "DISPLAY", "COMPUTE", "MDM SW", "MDM HW", "WLAN RF",
|
|
"WLAN BB", "DDR AUX", "ARC CPRF",
|
|
""
|
|
};
|
|
|
|
static int read_vx_data(struct vx_platform_data *pd, struct vx_log *log)
|
|
{
|
|
void __iomem *base = pd->base;
|
|
struct vx_header *hdr = &log->header;
|
|
struct vx_data *data;
|
|
u32 *vx, val, itr = 0;
|
|
int i, j, k;
|
|
|
|
val = read_word(base, itr);
|
|
if (!val)
|
|
return -ENOENT;
|
|
|
|
hdr->mode.type = val & VX_MODE_MASK_TYPE;
|
|
hdr->mode.logsize = (val >> VX_MODE_SHIFT_LOGSIZE) &
|
|
VX_MODE_MASK_LOGSIZE;
|
|
|
|
val = read_word(base, itr);
|
|
if (!val)
|
|
return -ENOENT;
|
|
|
|
hdr->flags.dur_ms = val & VX_FLAG_MASK_DUR;
|
|
hdr->flags.ts_shift = (val >> VX_FLAG_SHIFT_TS) & VX_FLAG_MASK_TS;
|
|
hdr->flags.flush_threshold = (val >> VX_FLAG_SHIFT_FLUSH_THRESH) &
|
|
VX_FLAG_MASK_FLUSH_THRESH;
|
|
|
|
data = kcalloc(hdr->mode.logsize, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < hdr->mode.logsize; i++) {
|
|
data[i].ts = read_word(base, itr);
|
|
if (!data[i].ts)
|
|
break;
|
|
data[i].ts <<= hdr->flags.ts_shift;
|
|
vx = kcalloc(ALIGN(pd->ndrv, 4), sizeof(*vx), GFP_KERNEL);
|
|
if (!vx)
|
|
goto no_mem;
|
|
|
|
for (j = 0; j < pd->ndrv;) {
|
|
val = read_word(base, itr);
|
|
for (k = 0; k < 4; k++)
|
|
vx[j++] = val >> (8 * k) & 0xFF;
|
|
}
|
|
data[i].drv_vx = vx;
|
|
}
|
|
|
|
log->data = data;
|
|
log->loglines = i;
|
|
|
|
return 0;
|
|
no_mem:
|
|
for (j = 0; j < i; j++)
|
|
kfree(data[j].drv_vx);
|
|
kfree(data);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void show_vx_data(struct vx_platform_data *pd, struct vx_log *log,
|
|
struct seq_file *seq)
|
|
{
|
|
int i, j;
|
|
struct vx_header *hdr = &log->header;
|
|
struct vx_data *data;
|
|
u32 prev;
|
|
bool from_exit = false;
|
|
|
|
seq_printf(seq, "Mode : %s\n"
|
|
"Duration (ms) : %u\n"
|
|
"Time Shift : %u\n"
|
|
"Flush Threshold: %u\n"
|
|
"Max Log Entries: %u\n",
|
|
MODE_STR(hdr->mode.type),
|
|
hdr->flags.dur_ms,
|
|
hdr->flags.ts_shift,
|
|
hdr->flags.flush_threshold,
|
|
hdr->mode.logsize);
|
|
|
|
seq_puts(seq, "Timestamp|");
|
|
|
|
for (i = 0; i < pd->ndrv; i++)
|
|
seq_printf(seq, "%*s|", 8, pd->drvs[i]);
|
|
seq_puts(seq, "\n");
|
|
|
|
for (i = 0; i < log->loglines; i++) {
|
|
data = &log->data[i];
|
|
seq_printf(seq, "%*x|", 9, data->ts);
|
|
/* An all-zero line indicates we entered LPM */
|
|
for (j = 0, prev = data->drv_vx[0]; j < pd->ndrv; j++)
|
|
prev |= data->drv_vx[j];
|
|
if (!prev) {
|
|
if (!from_exit) {
|
|
seq_printf(seq, "%s Enter\n", MODE_STR(hdr->mode.type));
|
|
from_exit = true;
|
|
} else {
|
|
seq_printf(seq, "%s Exit\n", MODE_STR(hdr->mode.type));
|
|
from_exit = false;
|
|
}
|
|
continue;
|
|
}
|
|
for (j = 0; j < pd->ndrv; j++)
|
|
seq_printf(seq, "%*u|", 8, data->drv_vx[j]);
|
|
seq_puts(seq, "\n");
|
|
}
|
|
}
|
|
|
|
static int vx_show(struct seq_file *seq, void *data)
|
|
{
|
|
struct vx_platform_data *pd = seq->private;
|
|
struct vx_log log;
|
|
int ret;
|
|
int i;
|
|
|
|
/*
|
|
* Read the data into memory to allow for
|
|
* post processing of data and present it
|
|
* cleanly.
|
|
*/
|
|
ret = read_vx_data(pd, &log);
|
|
if (ret)
|
|
return ret;
|
|
|
|
show_vx_data(pd, &log, seq);
|
|
|
|
for (i = 0; i < log.loglines; i++)
|
|
kfree(log.data[i].drv_vx);
|
|
kfree(log.data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int open_vx(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, vx_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations sys_pm_vx_fops = {
|
|
.open = open_vx,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int vx_create_debug_nodes(struct vx_platform_data *pd)
|
|
{
|
|
struct dentry *pf;
|
|
|
|
pf = debugfs_create_file("sys_pm_violators", 0400, NULL,
|
|
pd, &sys_pm_vx_fops);
|
|
if (!pf)
|
|
return -EINVAL;
|
|
|
|
pd->vx_file = pf;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id drv_match_table[] = {
|
|
{ .compatible = "qcom,sys-pm-lahaina",
|
|
.data = drv_names_lahaina },
|
|
{ }
|
|
};
|
|
|
|
static int vx_probe(struct platform_device *pdev)
|
|
{
|
|
const struct of_device_id *match_id;
|
|
struct vx_platform_data *pd;
|
|
const char **drvs;
|
|
int i, ret;
|
|
|
|
pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL);
|
|
if (!pd)
|
|
return -ENOMEM;
|
|
|
|
pd->base = of_iomap(pdev->dev.of_node, 0);
|
|
if (IS_ERR_OR_NULL(pd->base))
|
|
return PTR_ERR(pd->base);
|
|
|
|
match_id = of_match_node(drv_match_table, pdev->dev.of_node);
|
|
if (!match_id)
|
|
return -ENODEV;
|
|
|
|
drvs = (const char **)match_id->data;
|
|
for (i = 0; ; i++) {
|
|
const char *name = (const char *)drvs[i];
|
|
|
|
if (!name[0])
|
|
break;
|
|
}
|
|
pd->ndrv = i;
|
|
pd->drvs = drvs;
|
|
|
|
ret = vx_create_debug_nodes(pd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
platform_set_drvdata(pdev, pd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vx_remove(struct platform_device *pdev)
|
|
{
|
|
struct vx_platform_data *pd = platform_get_drvdata(pdev);
|
|
|
|
debugfs_remove(pd->vx_file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id vx_table[] = {
|
|
{ .compatible = "qcom,sys-pm-violators" },
|
|
{ }
|
|
};
|
|
|
|
static struct platform_driver vx_driver = {
|
|
.probe = vx_probe,
|
|
.remove = vx_remove,
|
|
.driver = {
|
|
.name = "sys-pm-violators",
|
|
.of_match_table = vx_table,
|
|
},
|
|
};
|
|
module_platform_driver(vx_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MSM System PM Violators Driver");
|
|
MODULE_ALIAS("platform:msm_sys_pm_vx");
|