mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 19:47:08 +02:00
Merge "cpufreq: qcom-hw: Add cycle_counter support"
This commit is contained in:
commit
5810827cc9
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
230
drivers/cpufreq/qcom-cpufreq-hw-debug.c
Normal file
230
drivers/cpufreq/qcom-cpufreq-hw-debug.c
Normal 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");
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
20
include/linux/qcom-cpufreq-hw.h
Normal file
20
include/linux/qcom-cpufreq-hw.h
Normal 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 */
|
||||
Loading…
Reference in New Issue
Block a user