From cc321fbbae19da41bb8224e6d8170d10f915b252 Mon Sep 17 00:00:00 2001 From: Naveen Yadav Date: Mon, 22 Jun 2020 17:51:06 +0530 Subject: [PATCH] cpufreq: qcom-cpufreq-hw: Add support for CPUFreq hardware debug Add support to print the perf state, pstate status and cycle counter status registers for various frequency domain and notifier to print cpufreq hardware debug registers during crash. Change-Id: I043f5a29869f505adf49e6adb62035ceb04d810a Signed-off-by: Naveen Yadav Signed-off-by: Mike Tipton Signed-off-by: Vivek Aknurwar Signed-off-by: Mike Tipton --- drivers/cpufreq/Kconfig.arm | 9 + drivers/cpufreq/Makefile | 1 + drivers/cpufreq/qcom-cpufreq-hw-debug.c | 230 ++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 drivers/cpufreq/qcom-cpufreq-hw-debug.c diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 954749afb5fe..46ad195e42f7 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -172,6 +172,15 @@ config ARM_QCOM_CPUFREQ_HW The driver implements the cpufreq interface for this HW engine. Say Y if you want to support CPUFreq HW. +config ARM_QCOM_CPUFREQ_HW_DEBUG + tristate "QCOM CPUFreq HW debug" + depends on ARM_QCOM_CPUFREQ_HW && DEBUG_FS + help + Support for the CPUFreq HW debug. + CPUFreq HW debug provide the support to print the CPUFreq HW debug + registers. + Say Y if you want to support CPUFreq HW Debug. + config ARM_RASPBERRYPI_CPUFREQ tristate "Raspberry Pi cpufreq support" depends on CLK_RASPBERRYPI || COMPILE_TEST diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index aba3a09fd633..d477861ad540 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o obj-$(CONFIG_ARM_QCOM_CPUFREQ_HW) += qcom-cpufreq-hw.o +obj-$(CONFIG_ARM_QCOM_CPUFREQ_HW_DEBUG) += qcom-cpufreq-hw-debug.o obj-$(CONFIG_ARM_QCOM_CPUFREQ_NVMEM) += qcom-cpufreq-nvmem.o obj-$(CONFIG_ARM_RASPBERRYPI_CPUFREQ) += raspberrypi-cpufreq.o obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o diff --git a/drivers/cpufreq/qcom-cpufreq-hw-debug.c b/drivers/cpufreq/qcom-cpufreq-hw-debug.c new file mode 100644 index 000000000000..292b0ac7c17e --- /dev/null +++ b/drivers/cpufreq/qcom-cpufreq-hw-debug.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "cpufreq_hw_debug: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum debug_hw_regs_data { + REG_PERF_STATE, + REG_CYCLE_CNTR, + REG_PSTATE_STATUS, + + REG_ARRAY_SIZE, +}; + +struct cpufreq_hwregs { + void __iomem *base[REG_ARRAY_SIZE]; + int domain_cnt; + struct dentry *debugfs_base; +}; + +struct cpufreq_register_data { + char *name; + u16 offset; +}; + +static struct cpufreq_hwregs *hw_regs; +static const u16 *offsets; + +static const u16 cpufreq_qcom_std_data[REG_ARRAY_SIZE] = { + [REG_PERF_STATE] = 0x920, + [REG_CYCLE_CNTR] = 0x9c0, + [REG_PSTATE_STATUS] = 0x700, +}; + +static const u16 cpufreq_qcom_std_epss_data[REG_ARRAY_SIZE] = { + [REG_PERF_STATE] = 0x320, + [REG_CYCLE_CNTR] = 0x3c4, + [REG_PSTATE_STATUS] = 0x020, +}; + +static int print_cpufreq_hw_debug_regs(struct seq_file *s, void *unused) +{ + int i, j; + u32 regval; + + static struct cpufreq_register_data data[] = { + {"PERF_STATE_DESIRED", REG_PERF_STATE}, + {"CYCLE_CNTR_VAL", REG_CYCLE_CNTR}, + {"PSTATE_STATUS", REG_PSTATE_STATUS}, + }; + + for (i = 0; i < hw_regs->domain_cnt; i++) { + seq_printf(s, "FREQUENCY DOMAIN %d\n", i); + for (j = 0; j < ARRAY_SIZE(data); j++) { + regval = readl_relaxed(hw_regs->base[i] + + offsets[data[j].offset]); + seq_printf(s, "%25s: 0x%.8x\n", data[j].name, regval); + } + } + + return 0; +} + +static int print_cpufreq_hw_reg_open(struct inode *inode, struct file *file) +{ + return single_open(file, print_cpufreq_hw_debug_regs, NULL); +} + +static const struct file_operations cpufreq_debug_register_fops = { + .open = print_cpufreq_hw_reg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int cpufreq_panic_callback(struct notifier_block *nfb, + unsigned long event, void *unused) +{ + int i, j; + u32 regval; + + static struct cpufreq_register_data data[] = { + {"PERF_STATE_DESIRED", REG_PERF_STATE}, + {"CYCLE_CNTR_VAL", REG_CYCLE_CNTR}, + {"PSTATE_STATUS", REG_PSTATE_STATUS}, + }; + + for (i = 0; i < hw_regs->domain_cnt; i++) { + pr_err("FREQUENCY DOMAIN %d\n", i); + for (j = 0; j < ARRAY_SIZE(data); j++) { + regval = readl_relaxed(hw_regs->base[i] + + offsets[data[j].offset]); + pr_err("%25s: 0x%.8x\n", data[j].name, regval); + } + } + + return NOTIFY_OK; +} + +static struct notifier_block cpufreq_panic_notifier = { + .notifier_call = cpufreq_panic_callback, + .priority = 1, +}; + +static int cpufreq_get_hwregs(struct platform_device *pdev) +{ + struct of_phandle_args args; + struct property *prop; + struct resource res; + void __iomem *base; + int i, ret; + + offsets = of_device_get_match_data(&pdev->dev); + if (!offsets) + return -EINVAL; + + hw_regs = devm_kzalloc(&pdev->dev, sizeof(*hw_regs), GFP_KERNEL); + if (!hw_regs) + return -ENOMEM; + + prop = of_find_property(pdev->dev.of_node, "qcom,freq-hw-domain", NULL); + if (!prop) + return -EINVAL; + + hw_regs->domain_cnt = prop->length / (2 * sizeof(prop->length)); + + for (i = 0; i < hw_regs->domain_cnt; i++) { + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "qcom,freq-hw-domain", 1, i, &args); + of_node_put(pdev->dev.of_node); + if (ret) + return ret; + + ret = of_address_to_resource(args.np, args.args[0], &res); + if (ret) + return ret; + + base = devm_ioremap(&pdev->dev, res.start, resource_size(&res)); + if (!base) + return -ENOMEM; + + hw_regs->base[i] = base; + } + + atomic_notifier_chain_register(&panic_notifier_list, + &cpufreq_panic_notifier); + + return 0; +} + +static int enable_cpufreq_hw_debug(struct platform_device *pdev) +{ + int ret; + + ret = cpufreq_get_hwregs(pdev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to map cpufreq hw regs\n"); + return ret; + } + + hw_regs->debugfs_base = debugfs_create_dir("qcom-cpufreq-hw", NULL); + if (!hw_regs->debugfs_base) { + dev_err(&pdev->dev, "Failed to create debugfs entry\n"); + return -ENODEV; + } + + if (!debugfs_create_file("print_cpufreq_debug_regs", 0444, + hw_regs->debugfs_base, NULL, &cpufreq_debug_register_fops)) + goto debugfs_fail; + + return 0; + +debugfs_fail: + dev_err(&pdev->dev, "Failed to create debugfs entry so cleaning up\n"); + debugfs_remove_recursive(hw_regs->debugfs_base); + return -ENODEV; +} + +static int qcom_cpufreq_hw_debug_probe(struct platform_device *pdev) +{ + return enable_cpufreq_hw_debug(pdev); +} + +static int qcom_cpufreq_hw_debug_remove(struct platform_device *pdev) +{ + debugfs_remove_recursive(hw_regs->debugfs_base); + return 0; +} + +static const struct of_device_id qcom_cpufreq_hw_debug_match[] = { + { .compatible = "qcom,cpufreq-hw-debug", + .data = &cpufreq_qcom_std_data }, + { .compatible = "qcom,cpufreq-hw-epss-debug", + .data = &cpufreq_qcom_std_epss_data }, + {} +}; + +static struct platform_driver qcom_cpufreq_hw_debug = { + .probe = qcom_cpufreq_hw_debug_probe, + .remove = qcom_cpufreq_hw_debug_remove, + .driver = { + .name = "qcom-cpufreq-hw-debug", + .of_match_table = qcom_cpufreq_hw_debug_match, + }, +}; + +static int __init qcom_cpufreq_hw_debug_init(void) +{ + return platform_driver_register(&qcom_cpufreq_hw_debug); +} +fs_initcall(qcom_cpufreq_hw_debug_init); + +static void __exit qcom_cpufreq_hw_debug_exit(void) +{ + return platform_driver_unregister(&qcom_cpufreq_hw_debug); +} +module_exit(qcom_cpufreq_hw_debug_exit); + +MODULE_DESCRIPTION("QTI clock driver for CPUFREQ HW debug"); +MODULE_LICENSE("GPL");