Merge "cpufreq: qcom-hw: Add cycle_counter support"

This commit is contained in:
qctecmdr 2022-08-15 15:08:20 -07:00 committed by Gerrit - the friendly Code Review server
commit 5810827cc9
5 changed files with 323 additions and 2 deletions

View File

@ -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

View File

@ -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

View File

@ -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 <linux/debugfs.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/panic_notifier.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
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");

View File

@ -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 <linux/bitfield.h>
@ -15,6 +15,7 @@
#include <linux/pm_opp.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/qcom-cpufreq-hw.h>
#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[] = {
@ -597,9 +658,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);

View File

@ -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 */