From e0f4f032b5bef6f8dab40252114b2469481078bf Mon Sep 17 00:00:00 2001 From: Mike Tipton Date: Fri, 15 Oct 2021 11:50:37 -0700 Subject: [PATCH 1/3] cpufreq: qcom-hw: Fix panic when offlining core under lmh throttle The qcom_lmh_dcvs_notify() function requires the CPU OPP tables, so cancel the lmh work before removing them. Change-Id: I3d42362baff1632da6d8a9cfc5ba9b724da240d9 Signed-off-by: Mike Tipton --- drivers/cpufreq/qcom-cpufreq-hw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index 36c79580fba2..79d83fbf3508 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2018, 2021, The Linux Foundation. All rights reserved. */ #include @@ -597,9 +597,9 @@ static int qcom_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) struct resource *res = data->res; void __iomem *base = data->base; + qcom_cpufreq_hw_lmh_exit(data); dev_pm_opp_remove_all_dynamic(cpu_dev); dev_pm_opp_of_cpumask_remove_table(policy->related_cpus); - qcom_cpufreq_hw_lmh_exit(data); kfree(policy->freq_table); kfree(data); iounmap(base); From cc321fbbae19da41bb8224e6d8170d10f915b252 Mon Sep 17 00:00:00 2001 From: Naveen Yadav Date: Mon, 22 Jun 2020 17:51:06 +0530 Subject: [PATCH 2/3] 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"); From cb0c7a7d3afd4e9472083de4ffbaca106771b419 Mon Sep 17 00:00:00 2001 From: Sai Harshini Nimmala Date: Thu, 14 Oct 2021 14:22:11 -0700 Subject: [PATCH 3/3] cpufreq: qcom-hw: Add cycle_counter support Add support for qcom_cpufreq_get_cpu_cycle_counter(), which provides the total accumulated CPU cycle counts for use by the scheduler. This code is a modified snapshot from msm-5.10 commit 83dbc79da4b1 ("qcom-cpufreq: remove references to WALT"). Change-Id: Id3ea9a4113cbe2581d159c780ab5d4b71e4d0edb Signed-off-by: Sai Harshini Nimmala Signed-off-by: Mike Tipton --- drivers/cpufreq/qcom-cpufreq-hw.c | 61 +++++++++++++++++++++++++++++++ include/linux/qcom-cpufreq-hw.h | 20 ++++++++++ 2 files changed, 81 insertions(+) create mode 100644 include/linux/qcom-cpufreq-hw.h diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index 79d83fbf3508..93035db9a5e7 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -15,6 +15,7 @@ #include #include #include +#include #define LUT_MAX_ENTRIES 40U #define LUT_SRC GENMASK(31, 30) @@ -28,6 +29,17 @@ #define HZ_PER_KHZ 1000 +#define CYCLE_CNTR_OFFSET(c, m, acc_count) \ + (acc_count ? ((c - cpumask_first(m) + 1) * 4) : 0) + +struct cpufreq_counter { + u64 total_cycle_counter; + u32 prev_cycle_counter; + spinlock_t lock; +}; + +static struct cpufreq_counter qcom_cpufreq_counter[NR_CPUS]; + struct qcom_cpufreq_soc_data { u32 reg_enable; u32 reg_domain_state; @@ -37,7 +49,9 @@ struct qcom_cpufreq_soc_data { u32 reg_intr_clr; u32 reg_current_vote; u32 reg_perf_state; + u32 reg_cycle_cntr; u8 lut_row_size; + bool accumulative_counter; }; struct qcom_cpufreq_data { @@ -103,6 +117,49 @@ static int qcom_cpufreq_update_opp(struct device *cpu_dev, return dev_pm_opp_enable(cpu_dev, freq_hz); } +u64 qcom_cpufreq_get_cpu_cycle_counter(int cpu) +{ + const struct qcom_cpufreq_soc_data *soc_data; + struct cpufreq_counter *cpu_counter; + struct qcom_cpufreq_data *data; + struct cpufreq_policy *policy; + u64 cycle_counter_ret; + unsigned long flags; + u16 offset; + u32 val; + + policy = cpufreq_cpu_get_raw(cpu); + if (!policy) + return 0; + + data = policy->driver_data; + soc_data = data->soc_data; + + cpu_counter = &qcom_cpufreq_counter[cpu]; + spin_lock_irqsave(&cpu_counter->lock, flags); + + offset = CYCLE_CNTR_OFFSET(cpu, policy->related_cpus, + soc_data->accumulative_counter); + val = readl_relaxed(data->base + + soc_data->reg_cycle_cntr + offset); + + if (val < cpu_counter->prev_cycle_counter) { + /* Handle counter overflow */ + cpu_counter->total_cycle_counter += UINT_MAX - + cpu_counter->prev_cycle_counter + val; + cpu_counter->prev_cycle_counter = val; + } else { + cpu_counter->total_cycle_counter += val - + cpu_counter->prev_cycle_counter; + cpu_counter->prev_cycle_counter = val; + } + cycle_counter_ret = cpu_counter->total_cycle_counter; + spin_unlock_irqrestore(&cpu_counter->lock, flags); + + return cycle_counter_ret; +} +EXPORT_SYMBOL(qcom_cpufreq_get_cpu_cycle_counter); + static int qcom_cpufreq_hw_target_index(struct cpufreq_policy *policy, unsigned int index) { @@ -377,7 +434,9 @@ static const struct qcom_cpufreq_soc_data qcom_soc_data = { .reg_volt_lut = 0x114, .reg_current_vote = 0x704, .reg_perf_state = 0x920, + .reg_cycle_cntr = 0x9c0, .lut_row_size = 32, + .accumulative_counter = true, }; static const struct qcom_cpufreq_soc_data epss_soc_data = { @@ -388,7 +447,9 @@ static const struct qcom_cpufreq_soc_data epss_soc_data = { .reg_volt_lut = 0x200, .reg_intr_clr = 0x308, .reg_perf_state = 0x320, + .reg_cycle_cntr = 0x3c4, .lut_row_size = 4, + .accumulative_counter = false, }; static const struct of_device_id qcom_cpufreq_hw_match[] = { diff --git a/include/linux/qcom-cpufreq-hw.h b/include/linux/qcom-cpufreq-hw.h new file mode 100644 index 000000000000..92aaf3493814 --- /dev/null +++ b/include/linux/qcom-cpufreq-hw.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021 Qualcomm Innovation Center, Inc. All rights reserved. + */ +#ifndef _LINUX_QCOM_CPUFREQ_HW_H +#define _LINUX_QCOM_CPUFREQ_HW_H + +/* + * We can take this out if we could move the OSM cycle + * counter to WALT scheduler? + */ +#if IS_ENABLED(CONFIG_ARM_QCOM_CPUFREQ_HW) +extern u64 qcom_cpufreq_get_cpu_cycle_counter(int cpu); +#else +static inline u64 qcom_cpufreq_get_cpu_cycle_counter(int cpu) +{ + return U64_MAX; +} +#endif /*CONFIG_ARM_QCOM_CPUFREQ_HW*/ +#endif /* _LINUX_QCOM_CPUFREQ_HW_H */