Loading drivers/soc/qcom/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -716,6 +716,15 @@ config QTI_DDR_STATS_LOG frequency residency and counts. The driver outputs information using sysfs. config QTI_SYS_PM_VX tristate "Qualcomm Technologies Inc (QTI) System PM Violators Driver" depends on QMP_DEBUGFS_CLIENT help This option enables debug subystems that prevent system low power modes. The user sends a QMP message to AOP to record subsystems preventing deeper system low power modes. The data is stored in the MSGRAM by AOP and read and reported in the debugfs by this driver. config MSM_JTAGV8 bool "Debug and ETM trace support across power collapse for ARMv8" default y if CORESIGHT_SOURCE_ETM4X Loading drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -84,3 +84,4 @@ obj-$(CONFIG_QTI_HW_KEY_MANAGER) += hwkm.o crypto-qti-hwkm.o ifdef CONFIG_DEBUG_FS obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd-debug.o endif obj-$(CONFIG_QTI_SYS_PM_VX) += sys_pm_vx.o drivers/soc/qcom/sys_pm_vx.c 0 → 100644 +300 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2020, 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); \ 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; 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; 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) { seq_printf(seq, "%s Enter", MODE_STR(hdr->mode.type)); 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"); Loading
drivers/soc/qcom/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -716,6 +716,15 @@ config QTI_DDR_STATS_LOG frequency residency and counts. The driver outputs information using sysfs. config QTI_SYS_PM_VX tristate "Qualcomm Technologies Inc (QTI) System PM Violators Driver" depends on QMP_DEBUGFS_CLIENT help This option enables debug subystems that prevent system low power modes. The user sends a QMP message to AOP to record subsystems preventing deeper system low power modes. The data is stored in the MSGRAM by AOP and read and reported in the debugfs by this driver. config MSM_JTAGV8 bool "Debug and ETM trace support across power collapse for ARMv8" default y if CORESIGHT_SOURCE_ETM4X Loading
drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -84,3 +84,4 @@ obj-$(CONFIG_QTI_HW_KEY_MANAGER) += hwkm.o crypto-qti-hwkm.o ifdef CONFIG_DEBUG_FS obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd-debug.o endif obj-$(CONFIG_QTI_SYS_PM_VX) += sys_pm_vx.o
drivers/soc/qcom/sys_pm_vx.c 0 → 100644 +300 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2020, 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); \ 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; 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; 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) { seq_printf(seq, "%s Enter", MODE_STR(hdr->mode.type)); 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");