From 31d00274da27350cdce33db27cb45fe748a00a3d Mon Sep 17 00:00:00 2001 From: Naveen Yadav Date: Thu, 24 Oct 2019 14:40:12 +0530 Subject: [PATCH] cpufreq: qcom-hw: Add support for cpufreq hardware debug and trace The cpufreq hardware debug and trace enable the tracing for trace packets from various clock domains. Also it add the support to print the perf state, performance state and cycle counter status register for various frequency domain. Change-Id: I45a7aab7a6438b3fabea14b2a78b6a05db6f307d Signed-off-by: Naveen Yadav --- drivers/cpufreq/Kconfig.arm | 10 + drivers/cpufreq/Makefile | 1 + drivers/cpufreq/qcom-cpufreq-hw-debug.c | 591 ++++++++++++++++++++++++ 3 files changed, 602 insertions(+) create mode 100644 drivers/cpufreq/qcom-cpufreq-hw-debug.c diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 586424506baa..2ca0cabe041b 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -310,6 +310,16 @@ 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 + bool "QCOM CPUFreq HW debug" + depends on ARM_QCOM_CPUFREQ_HW + help + Support for the CPUFreq HW debug. + CPUFreq HW debug provide the support to capture trace packets for + various clock domain and trace type could be set to periodic and + xor if applicable. + Say Y if you want to support CPUFreq HW Trace. + config CPU_FREQ_MSM bool "MSM CPUFreq support" depends on CPU_FREQ diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index cc986033e55a..addf17b5bec6 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -91,6 +91,7 @@ obj-$(CONFIG_ARM_TEGRA186_CPUFREQ) += tegra186-cpufreq.o obj-$(CONFIG_ARM_TI_CPUFREQ) += ti-cpufreq.o obj-$(CONFIG_ARM_VEXPRESS_SPC_CPUFREQ) += vexpress-spc-cpufreq.o obj-$(CONFIG_ARM_QCOM_CPUFREQ_HW) += qcom-cpufreq-hw.o +obj-$(CONFIG_ARM_QCOM_CPUFREQ_HW_DEBUG) += qcom-cpufreq-hw-debug.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..85c8101a4ac3 --- /dev/null +++ b/drivers/cpufreq/qcom-cpufreq-hw-debug.c @@ -0,0 +1,591 @@ +// 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 +#include + +enum trace_type { + XOR_PACKET, + PERIODIC_PACKET, +}; + +enum clock_domain { + CLKDOM0, + CLKDOM1, + CLKDOM2, + CLKDOM3, + + CLK_DOMAIN_SIZE, +}; + +enum debug_trace_regs_data { + GLOBAL_CTRL_OFFSET, + CLKDOM_CTRL_OFFSET, + PERIODIC_TIMER_CTRL_OFFSET, + + PERIODIC_TRACE_ENABLE_BIT, + CPUFREQ_HW_TRACE_ENABLE_BIT, + + 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; +}; + +#define CLKDOM0_TRACE_PACKET_SHIFT 0 +#define CLKDOM1_TRACE_PACKET_SHIFT 3 +#define CLKDOM2_TRACE_PACKET_SHIFT 6 +#define CLKDOM3_TRACE_PACKET_SHIFT 9 +#define CLKDOM_TRACE_PACKET_WIDTH 2 +#define MAX_DEBUG_BUF_LEN 50 +#define MAX_PKT_SIZE 5 +#define CLKDOMAIN_SET_VAL(val, packet_sel, shift) \ + ((val & ~GENMASK(shift + CLKDOM_TRACE_PACKET_WIDTH, shift)) \ + | (packet_sel << shift)) +#define CLKDOMAIN_CLEAR_VAL(val, packet_sel, shift) \ + ((val & ~GENMASK(shift + CLKDOM_TRACE_PACKET_WIDTH, shift))) + +static struct cpufreq_hwregs *hw_regs; +static char debug_buf[MAX_DEBUG_BUF_LEN]; +static const u16 *offsets; + +static const u16 cpufreq_qcom_std_data[REG_ARRAY_SIZE] = { + [GLOBAL_CTRL_OFFSET] = 0x10, + [CLKDOM_CTRL_OFFSET] = 0x14, + [PERIODIC_TIMER_CTRL_OFFSET] = 0x1C, + [REG_PERF_STATE] = 0x920, + [REG_CYCLE_CNTR] = 0x9c0, + [REG_PSTATE_STATUS] = 0x700, + [CPUFREQ_HW_TRACE_ENABLE_BIT] = 16, + [PERIODIC_TRACE_ENABLE_BIT] = 17, +}; + +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 clock_timer_set(void *data, u64 val) +{ + u32 base; + int ret; + + if (!data) + return -EINVAL; + + base = *((u32 *)data); + + ret = scm_io_write(base + offsets[PERIODIC_TIMER_CTRL_OFFSET], val); + if (ret) + pr_err("failed(0x%x) to set clk timer\n", ret); + + return ret; +} + +static int clock_timer_get(void *data, u64 *val) +{ + u32 base; + + if (!data) + return -EINVAL; + + base = *((u32 *)data); + + *val = scm_io_read(base + offsets[PERIODIC_TIMER_CTRL_OFFSET]); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(clock_timer_fops, clock_timer_get, clock_timer_set, + "%llu\n"); + +static ssize_t trace_type_set(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + u32 regval, base; + int ret; + + if (IS_ERR(file) || file == NULL) { + pr_err("input error %ld\n", PTR_ERR(file)); + return -EINVAL; + } + + if (!file->private_data) + return -EINVAL; + + base = *((u32 *)file->private_data); + + if (count < MAX_DEBUG_BUF_LEN) { + if (copy_from_user(debug_buf, (void __user *) buf, + MAX_DEBUG_BUF_LEN)) + return -EFAULT; + } + debug_buf[count] = '\0'; + + regval = scm_io_read(base + offsets[GLOBAL_CTRL_OFFSET]); + if (!strcmp(debug_buf, "periodic\n")) { + regval |= BIT(offsets[PERIODIC_TRACE_ENABLE_BIT]); + } else if (!strcmp(debug_buf, "xor\n")) { + regval &= ~BIT(offsets[PERIODIC_TRACE_ENABLE_BIT]); + } else { + pr_err("error, supported trace mode types: 'periodic' or 'xor'\n"); + return -EINVAL; + } + + ret = scm_io_write(base + offsets[GLOBAL_CTRL_OFFSET], regval); + if (ret) + pr_err("failed(0x%x) to set cpufreq clk timer\n", ret); + + return count; +} + +static ssize_t trace_type_get(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int len; + u32 regval, base; + + if (IS_ERR(file) || file == NULL) { + pr_err("input error %ld\n", PTR_ERR(file)); + return -EINVAL; + } + + if (!file->private_data) + return -EINVAL; + + base = *((u32 *)file->private_data); + + regval = scm_io_read(base + offsets[GLOBAL_CTRL_OFFSET]); + if (regval && offsets[PERIODIC_TRACE_ENABLE_BIT]) + len = snprintf(debug_buf, sizeof(debug_buf), "periodic\n"); + else + len = snprintf(debug_buf, sizeof(debug_buf), "xor\n"); + + return simple_read_from_buffer((void __user *) buf, count, ppos, + (void *) debug_buf, len); +} + +static int trace_type_open(struct inode *inode, struct file *file) +{ + if (IS_ERR(file) || file == NULL) { + pr_err("input error %ld\n", PTR_ERR(file)); + return -EINVAL; + } + + file->private_data = inode->i_private; + + return 0; +} + +static const struct file_operations clk_trace_type_fops = { + .read = trace_type_get, + .open = trace_type_open, + .write = trace_type_set, +}; + +void __domain_packet_set(u32 *clkdom_regval, u32 *pktsel_regval, int clkdomain, + int pktsel, int packet_sel_shift, int enable) +{ + if (!!enable) { + *clkdom_regval |= BIT(clkdomain); + *pktsel_regval = CLKDOMAIN_SET_VAL(*pktsel_regval, pktsel, + packet_sel_shift); + } else { + *clkdom_regval &= ~BIT(clkdomain); + *pktsel_regval &= CLKDOMAIN_CLEAR_VAL(*pktsel_regval, pktsel, + packet_sel_shift); + } +} + +static ssize_t domain_packet_set(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + unsigned int filled, clk_domain, packet_sel, enable = 1; + u32 clkdom_regval, pktsel_regval, base; + char buf[MAX_DEBUG_BUF_LEN]; + int ret; + + if (IS_ERR(file) || file == NULL) { + pr_err("input error %ld\n", PTR_ERR(file)); + return -EINVAL; + } + + if (!file->private_data) + return -EINVAL; + + base = *((u32 *)file->private_data); + + if (count < MAX_DEBUG_BUF_LEN) { + if (copy_from_user(buf, ubuf, count)) + return -EFAULT; + } + + buf[count] = '\0'; + filled = sscanf(buf, "%u %u %u", &clk_domain, &packet_sel, &enable); + if (clk_domain > CLK_DOMAIN_SIZE || packet_sel > MAX_PKT_SIZE) { + pr_err("Clock domain and source selection not in range\n"); + return -EINVAL; + } + + clkdom_regval = scm_io_read(base + offsets[GLOBAL_CTRL_OFFSET]); + pktsel_regval = scm_io_read(base + offsets[CLKDOM_CTRL_OFFSET]); + + + switch (clk_domain) { + case CLKDOM0: + __domain_packet_set(&clkdom_regval, &pktsel_regval, CLKDOM0, + packet_sel, CLKDOM0_TRACE_PACKET_SHIFT, enable); + break; + case CLKDOM1: + __domain_packet_set(&clkdom_regval, &pktsel_regval, CLKDOM1, + packet_sel, CLKDOM1_TRACE_PACKET_SHIFT, enable); + break; + case CLKDOM2: + __domain_packet_set(&clkdom_regval, &pktsel_regval, CLKDOM2, + packet_sel, CLKDOM2_TRACE_PACKET_SHIFT, enable); + break; + case CLKDOM3: + __domain_packet_set(&clkdom_regval, &pktsel_regval, CLKDOM3, + packet_sel, CLKDOM3_TRACE_PACKET_SHIFT, enable); + break; + default: + return -EINVAL; + } + + ret = scm_io_write(base + offsets[GLOBAL_CTRL_OFFSET], clkdom_regval); + if (ret) + pr_err("failed(0x%x) to set cpufreq domain\n", ret); + + ret = scm_io_write(base + offsets[CLKDOM_CTRL_OFFSET], pktsel_regval); + if (ret) + pr_err("failed(0x%x) to set cpufreq trace packet\n", ret); + + return count; +} + +static ssize_t domain_packet_get(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + u32 base, clkdom_regval, pktsel_regval; + int len; + + if (IS_ERR(file) || file == NULL) { + pr_err("input error %ld\n", PTR_ERR(file)); + return -EINVAL; + } + + if (!file->private_data) + return -EINVAL; + + base = *((u32 *)file->private_data); + + clkdom_regval = scm_io_read(base + offsets[GLOBAL_CTRL_OFFSET]); + pktsel_regval = scm_io_read(base + offsets[CLKDOM_CTRL_OFFSET]); + + len = snprintf(debug_buf, sizeof(debug_buf), + "GLOBAL_TRACE_CTRL = 0x%x\nCLKDOM_TRACE_CTRL = 0x%x\n", + clkdom_regval, pktsel_regval); + + return simple_read_from_buffer((void __user *) buf, count, ppos, + (void *) debug_buf, len); +} + +static int domain_packet_open(struct inode *inode, struct file *file) +{ + if (IS_ERR(file) || file == NULL) { + pr_err("input error %ld\n", PTR_ERR(file)); + return -EINVAL; + } + + file->private_data = inode->i_private; + + return 0; +} + +static const struct file_operations clock_domian_packet_set = { + .write = domain_packet_set, + .open = domain_packet_open, + .read = domain_packet_get, +}; + +static int cpufreq_hw_trace_enable_set(void *data, u64 val) +{ + u32 regval, base; + int ret; + + if (!data) + return -EINVAL; + + base = *((u32 *)data); + + regval = scm_io_read(base + offsets[GLOBAL_CTRL_OFFSET]); + if (!!val) + regval |= BIT(offsets[CPUFREQ_HW_TRACE_ENABLE_BIT]); + else + regval &= ~BIT(offsets[CPUFREQ_HW_TRACE_ENABLE_BIT]); + + ret = scm_io_write(base + offsets[GLOBAL_CTRL_OFFSET], regval); + if (ret) + pr_err("failed(0x%x) to set cpufreq hw trace\n", ret); + + return ret; +} + +static int cpufreq_hw_trace_enable_get(void *data, u64 *val) +{ + u32 regval, base; + + if (!data) + return -EINVAL; + + base = *((u32 *)data); + + regval = scm_io_read(base + offsets[GLOBAL_CTRL_OFFSET]); + *val = (regval & BIT(offsets[CPUFREQ_HW_TRACE_ENABLE_BIT])) >> + offsets[CPUFREQ_HW_TRACE_ENABLE_BIT]; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(clock_trace_enable, cpufreq_hw_trace_enable_get, + cpufreq_hw_trace_enable_set, "%llu\n"); + +static int print_cpufreq_hw_trace_regs(struct seq_file *s, void *unused) +{ + u32 base; + int i; + + static struct cpufreq_register_data data[] = { + {"GLOBAL_TRACE_CTRL", GLOBAL_CTRL_OFFSET}, + {"CLKDOM_TRACE_CTRL", CLKDOM_CTRL_OFFSET}, + {"PERIODIC_TIMER_TIMER", PERIODIC_TIMER_CTRL_OFFSET}, + }; + + if (!s->private) + return -EINVAL; + + base = *((u32 *)s->private); + + for (i = 0; i < ARRAY_SIZE(data); i++) { + seq_printf(s, "%25s: 0x%.8x\n", data[i].name, + scm_io_read(base + offsets[data[i].offset])); + } + + return 0; +} + +static int print_trace_reg_open(struct inode *inode, struct file *file) +{ + return single_open(file, print_cpufreq_hw_trace_regs, inode->i_private); +} + +static const struct file_operations cpufreq_trace_register_fops = { + .open = print_trace_reg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +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_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); + 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; + } + return 0; +} + +static int enable_cpufreq_hw_trace_debug(struct platform_device *pdev, + bool is_secure) +{ + struct resource *res; + void *base; + 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; + + if (!is_secure || of_device_is_compatible(pdev->dev.of_node, + "qcom,cpufreq-hw-epss-debug")) + return 0; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "domain-top"); + if (!res) { + dev_err(&pdev->dev, "Failed to get domain-top register base\n"); + return 0; + } + + base = &(res->start); + + if (!debugfs_create_file("print_cpufreq_trace_regs", 0444, + hw_regs->debugfs_base, base, &cpufreq_trace_register_fops)) + goto debugfs_fail; + + if (!debugfs_create_file("clock_domain_packet_sel", 0444, + hw_regs->debugfs_base, base, &clock_domian_packet_set)) + goto debugfs_fail; + + if (!debugfs_create_file("trace_enable", 0444, hw_regs->debugfs_base, + base, &clock_trace_enable)) + goto debugfs_fail; + + if (!debugfs_create_file("clock_timer", 0444, + hw_regs->debugfs_base, base, &clock_timer_fops)) + goto debugfs_fail; + + if (!debugfs_create_file("trace_type", 0444, + hw_regs->debugfs_base, base, &clk_trace_type_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) +{ + bool is_secure = scm_is_secure_device(); + + return enable_cpufreq_hw_trace_debug(pdev, is_secure); +} + +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_trace_match[] = { + { .compatible = "qcom,cpufreq-hw-debug-trace", + .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_trace_match, + }, +}; + +static int __init qcom_cpufreq_hw_debug_trace_init(void) +{ + return platform_driver_register(&qcom_cpufreq_hw_debug); +} +fs_initcall(qcom_cpufreq_hw_debug_trace_init); + +static void __exit qcom_cpufreq_hw_debug_trace_exit(void) +{ + return platform_driver_unregister(&qcom_cpufreq_hw_debug); +} +module_exit(qcom_cpufreq_hw_debug_trace_exit); + +MODULE_DESCRIPTION("QTI clock driver for CPUFREQ HW debug"); +MODULE_LICENSE("GPL v2");