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");