diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index c44247d0b83e..fcada610b3b1 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -23,6 +23,15 @@ config COMMON_CLK menu "Common Clock Framework" depends on COMMON_CLK +config COMMON_CLK_QCOM_DEBUG + bool "QCOM clock debug features" + depends on DEBUG_FS && QGKI + help + Support for QCOM debug features. These features include + modifications to existing debugfs nodes to make them writable (e.g. + clk_enable_count, clk_rate), as well as new debugfs nodes (e.g. + clk_enabled_list, debug_suspend, trace_clocks). + config COMMON_CLK_WM831X tristate "Clock driver for WM831x/2x PMICs" depends on MFD_WM831X diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 98cb6f6daf8e..bde91b2284e6 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -3127,6 +3127,76 @@ static int clk_max_rate_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(clk_max_rate); +static int clock_debug_rate_set(void *data, u64 val) +{ + struct clk_core *core = data; + int ret; + + ret = clk_set_rate(core->hw->clk, val); + if (ret) + pr_err("clk_set_rate(%lu) failed (%d)\n", + (unsigned long)val, ret); + + return ret; +} + +static int clock_debug_rate_get(void *data, u64 *val) +{ + struct clk_core *core = data; + + *val = clk_get_rate(core->hw->clk); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get, + clock_debug_rate_set, "%llu\n"); + +static ssize_t clock_parent_read(struct file *filp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + char name[256] = {0}; + struct clk_core *core = filp->private_data; + struct clk_core *p = core->hw->core->parent; + + snprintf(name, sizeof(name), "%s\n", p ? p->name : "None\n"); + + return simple_read_from_buffer(ubuf, cnt, ppos, name, strlen(name)); +} + +static const struct file_operations clock_parent_fops = { + .open = simple_open, + .read = clock_parent_read, +}; + +static int clock_debug_enable_set(void *data, u64 val) +{ + struct clk_core *core = data; + int rc = 0; + + if (val) + rc = clk_prepare_enable(core->hw->clk); + else + clk_disable_unprepare(core->hw->clk); + + return rc; +} + +static int clock_debug_enable_get(void *data, u64 *val) +{ + struct clk_core *core = data; + int enabled = 0; + + enabled = core->enable_count; + + *val = enabled; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get, + clock_debug_enable_set, "%lld\n"); + static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) { struct dentry *root; @@ -3137,14 +3207,15 @@ static void clk_debug_create_one(struct clk_core *core, struct dentry *pdentry) root = debugfs_create_dir(core->name, pdentry); core->dentry = root; - debugfs_create_ulong("clk_rate", 0444, root, &core->rate); + debugfs_create_file("clk_rate", 0444, root, core, &clock_rate_fops); debugfs_create_file("clk_min_rate", 0444, root, core, &clk_min_rate_fops); debugfs_create_file("clk_max_rate", 0444, root, core, &clk_max_rate_fops); debugfs_create_ulong("clk_accuracy", 0444, root, &core->accuracy); debugfs_create_u32("clk_phase", 0444, root, &core->phase); debugfs_create_file("clk_flags", 0444, root, core, &clk_flags_fops); debugfs_create_u32("clk_prepare_count", 0444, root, &core->prepare_count); - debugfs_create_u32("clk_enable_count", 0444, root, &core->enable_count); + debugfs_create_file("clk_enable_count", 0444, root, core, + &clock_enable_fops); debugfs_create_u32("clk_protect_count", 0444, root, &core->protect_count); debugfs_create_u32("clk_notifier_count", 0444, root, &core->notifier_count); debugfs_create_file("clk_duty_cycle", 0444, root, core, @@ -3196,6 +3267,156 @@ static void clk_debug_unregister(struct clk_core *core) mutex_unlock(&clk_debug_lock); } +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG +#define clock_debug_output(m, fmt, ...) \ +do { \ + if (m) \ + seq_printf(m, fmt, ##__VA_ARGS__); \ + else \ + pr_info(fmt, ##__VA_ARGS__); \ +} while (0) + +static int clock_debug_print_clock(struct clk_core *c, struct seq_file *s) +{ + char *start = "\t"; + struct clk *clk; + + if (!c || !c->prepare_count) + return 0; + + clk = c->hw->clk; + + do { + c = clk->core; + if (c->ops->list_rate_vdd_level) + clock_debug_output(s, "%s%s:%u:%u [%ld, %d]", start, + c->name, + c->prepare_count, + c->enable_count, + c->rate, + c->ops->list_rate_vdd_level(c->hw, c->rate)); + else + clock_debug_output(s, "%s%s:%u:%u [%ld]", start, + c->name, + c->prepare_count, + c->enable_count, + c->rate); + start = " -> "; + } while (s && (clk = clk_get_parent(clk))); + + if (s) + clock_debug_output(s, "\n"); + + return 1; +} + +/* + * clock_debug_print_enabled_clocks() - Print names of enabled clocks + */ +static void clock_debug_print_enabled_clocks(struct seq_file *s) +{ + struct clk_core *core; + int cnt = 0; + + clock_debug_output(s, "Enabled clocks:\n"); + + hlist_for_each_entry(core, &clk_debug_list, debug_node) + cnt += clock_debug_print_clock(core, s); + + if (cnt) + clock_debug_output(s, "Enabled clock count: %d\n", cnt); + else + clock_debug_output(s, "No clocks enabled.\n"); +} + +static int enabled_clocks_show(struct seq_file *s, void *unused) +{ + mutex_lock(&clk_debug_lock); + clock_debug_print_enabled_clocks(s); + mutex_unlock(&clk_debug_lock); + + return 0; +} + +static int enabled_clocks_open(struct inode *inode, struct file *file) +{ + return single_open(file, enabled_clocks_show, inode->i_private); +} + +static const struct file_operations clk_enabled_list_fops = { + .open = enabled_clocks_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static u32 debug_suspend; + +/* + * Print the names of all enabled clocks and their parents if + * debug_suspend is set from debugfs. + */ +void clock_debug_print_enabled(void) +{ + if (likely(!debug_suspend)) + return; + + if (!mutex_trylock(&clk_debug_lock)) + return; + + clock_debug_print_enabled_clocks(NULL); + mutex_unlock(&clk_debug_lock); +} +EXPORT_SYMBOL(clock_debug_print_enabled); + +static void clk_state_subtree(struct clk_core *c) +{ + int vdd_level = 0; + struct clk_core *child; + + if (!c) + return; + + if (c->ops->list_rate_vdd_level) + vdd_level = c->ops->list_rate_vdd_level(c->hw, c->rate); + + trace_clk_state(c->name, c->prepare_count, c->enable_count, + c->rate, vdd_level); + + hlist_for_each_entry(child, &c->children, child_node) + clk_state_subtree(child); +} + +static int clk_state_show(struct seq_file *s, void *data) +{ + struct clk_core *c; + struct hlist_head **lists = (struct hlist_head **)s->private; + + clk_prepare_lock(); + + for (; *lists; lists++) + hlist_for_each_entry(c, *lists, child_node) + clk_state_subtree(c); + + clk_prepare_unlock(); + + return 0; +} + + +static int clk_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_state_show, inode->i_private); +} + +static const struct file_operations clk_state_fops = { + .open = clk_state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + /** * clk_debug_init - lazily populate the debugfs clk directory * @@ -3220,6 +3441,14 @@ static int __init clk_debug_init(void) debugfs_create_file("clk_orphan_dump", 0444, rootdir, &orphan_list, &clk_dump_fops); +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + debugfs_create_file("clk_enabled_list", 0444, rootdir, + &clk_debug_list, &clk_enabled_list_fops); + debugfs_create_u32("debug_suspend", 0644, rootdir, &debug_suspend); + debugfs_create_file("trace_clocks", 0444, rootdir, &all_lists, + &clk_state_fops); +#endif + mutex_lock(&clk_debug_lock); hlist_for_each_entry(core, &clk_debug_list, debug_node) clk_debug_create_one(core, rootdir); diff --git a/drivers/clk/clk.h b/drivers/clk/clk.h index 2d801900cad5..99a02ba422fd 100644 --- a/drivers/clk/clk.h +++ b/drivers/clk/clk.h @@ -25,6 +25,10 @@ struct clk_hw *clk_find_hw(const char *dev_id, const char *con_id); struct clk *clk_hw_create_clk(struct device *dev, struct clk_hw *hw, const char *dev_id, const char *con_id); void __clk_put(struct clk *clk); + +/* Debugfs API to print the enabled clocks */ +void clock_debug_print_enabled(void); + #else /* All these casts to avoid ifdefs in clkdev... */ static inline struct clk * diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c index af14004aa323..4ee77f31051a 100644 --- a/drivers/clk/qcom/clk-alpha-pll.c +++ b/drivers/clk/qcom/clk-alpha-pll.c @@ -11,6 +11,7 @@ #include #include "clk-alpha-pll.h" +#include "clk-debug.h" #include "common.h" #define PLL_MODE(p) ((p)->offset + 0x0) @@ -1331,6 +1332,53 @@ static void clk_zonda_5lpe_pll_disable(struct clk_hw *hw) return; } +static void clk_alpha_pll_list_registers(struct seq_file *f, struct clk_hw *hw) +{ + struct clk_alpha_pll *pll = to_clk_alpha_pll(hw); + int size, i, val; + + static struct clk_register_data data[] = { + {"PLL_MODE", 0x0}, + {"PLL_L_VAL", 0x4}, + {"PLL_ALPHA_VAL", 0x8}, + {"PLL_ALPHA_VAL_U", 0xC}, + {"PLL_USER_CTL", 0x10}, + {"PLL_CONFIG_CTL", 0x18}, + }; + + static struct clk_register_data data1[] = { + {"APSS_PLL_VOTE", 0x0}, + }; + + size = ARRAY_SIZE(data); + + for (i = 0; i < size; i++) { + regmap_read(pll->clkr.regmap, pll->offset + data[i].offset, + &val); + seq_printf(f, "%20s: 0x%.8x\n", data[i].name, val); + } + + regmap_read(pll->clkr.regmap, pll->offset + data[0].offset, &val); + + if (val & PLL_FSM_ENA) { + regmap_read(pll->clkr.regmap, pll->clkr.enable_reg + + data1[0].offset, &val); + seq_printf(f, "%20s: 0x%.8x\n", data1[0].name, val); + } +} + +static struct clk_regmap_ops clk_alpha_pll_regmap_ops = { + .list_registers = clk_alpha_pll_list_registers, +}; + +static void clk_alpha_pll_init(struct clk_hw *hw) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + + if (!rclk->ops) + rclk->ops = &clk_alpha_pll_regmap_ops; +} + const struct clk_ops clk_alpha_pll_ops = { .prepare = clk_prepare_regmap, .unprepare = clk_unprepare_regmap, @@ -1342,6 +1390,11 @@ const struct clk_ops clk_alpha_pll_ops = { .recalc_rate = clk_alpha_pll_recalc_rate, .round_rate = clk_alpha_pll_round_rate, .set_rate = clk_alpha_pll_set_rate, + .init = clk_alpha_pll_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_alpha_pll_ops); @@ -1356,6 +1409,9 @@ const struct clk_ops clk_alpha_pll_huayra_ops = { .recalc_rate = alpha_pll_huayra_recalc_rate, .round_rate = alpha_pll_huayra_round_rate, .set_rate = alpha_pll_huayra_set_rate, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_alpha_pll_huayra_ops); @@ -1370,6 +1426,10 @@ const struct clk_ops clk_alpha_pll_hwfsm_ops = { .recalc_rate = clk_alpha_pll_recalc_rate, .round_rate = clk_alpha_pll_round_rate, .set_rate = clk_alpha_pll_hwfsm_set_rate, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_alpha_pll_hwfsm_ops); @@ -1383,6 +1443,9 @@ const struct clk_ops clk_trion_fixed_pll_ops = { .is_enabled = clk_trion_pll_is_enabled, .recalc_rate = clk_trion_pll_recalc_rate, .round_rate = clk_trion_pll_round_rate, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_trion_fixed_pll_ops); @@ -1397,6 +1460,9 @@ const struct clk_ops clk_alpha_pll_zonda_ops = { .recalc_rate = clk_zonda_pll_recalc_rate, .round_rate = clk_alpha_pll_round_rate, .set_rate = clk_zonda_pll_set_rate, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL(clk_alpha_pll_zonda_ops); @@ -1681,6 +1747,9 @@ const struct clk_ops clk_alpha_pll_fabia_ops = { .set_rate = alpha_pll_fabia_set_rate, .recalc_rate = alpha_pll_fabia_recalc_rate, .round_rate = clk_alpha_pll_round_rate, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_alpha_pll_fabia_ops); @@ -1694,6 +1763,9 @@ const struct clk_ops clk_alpha_pll_fixed_fabia_ops = { .is_enabled = clk_alpha_pll_is_enabled, .recalc_rate = alpha_pll_fabia_recalc_rate, .round_rate = clk_alpha_pll_round_rate, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_alpha_pll_fixed_fabia_ops); @@ -2476,6 +2548,9 @@ const struct clk_ops clk_alpha_pll_lucid_ops = { .recalc_rate = alpha_pll_lucid_recalc_rate, .round_rate = clk_alpha_pll_round_rate, .set_rate = alpha_pll_lucid_set_rate, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL(clk_alpha_pll_lucid_ops); @@ -2490,6 +2565,10 @@ const struct clk_ops clk_alpha_pll_lucid_5lpe_ops = { .recalc_rate = alpha_pll_lucid_recalc_rate, .round_rate = clk_alpha_pll_round_rate, .set_rate = alpha_pll_lucid_5lpe_set_rate, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL(clk_alpha_pll_lucid_5lpe_ops); @@ -2503,6 +2582,9 @@ const struct clk_ops clk_alpha_pll_fixed_lucid_ops = { .is_enabled = alpha_pll_lucid_is_enabled, .recalc_rate = alpha_pll_lucid_recalc_rate, .round_rate = clk_alpha_pll_round_rate, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL(clk_alpha_pll_fixed_lucid_ops); @@ -2523,6 +2605,10 @@ const struct clk_ops clk_alpha_pll_fixed_lucid_5lpe_ops = { .is_enabled = alpha_pll_lucid_is_enabled, .recalc_rate = alpha_pll_lucid_recalc_rate, .round_rate = clk_alpha_pll_round_rate, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL(clk_alpha_pll_fixed_lucid_5lpe_ops); diff --git a/drivers/clk/qcom/clk-branch.c b/drivers/clk/qcom/clk-branch.c index 07f91876a250..5a29357997cc 100644 --- a/drivers/clk/qcom/clk-branch.c +++ b/drivers/clk/qcom/clk-branch.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2013, 2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2013, 2016-2019, The Linux Foundation. All rights reserved. */ #include @@ -14,6 +14,7 @@ #include "clk-branch.h" #include "clk-debug.h" +#include "clk-regmap.h" static bool clk_branch_in_hwcg_mode(const struct clk_branch *br) { @@ -124,10 +125,17 @@ static void clk_branch_disable(struct clk_hw *hw) clk_branch_toggle(hw, false, clk_branch_check_halt); } +static void clk_branch_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + clk_common_debug_init(hw, dentry); + clk_debug_measure_add(hw, dentry); +} + const struct clk_ops clk_branch_ops = { .enable = clk_branch_enable, .disable = clk_branch_disable, .is_enabled = clk_is_enabled_regmap, + .debug_init = clk_branch_debug_init, }; EXPORT_SYMBOL_GPL(clk_branch_ops); @@ -141,17 +149,69 @@ static void clk_branch2_disable(struct clk_hw *hw) clk_branch_toggle(hw, false, clk_branch2_check_halt); } +static void clk_branch2_list_registers(struct seq_file *f, struct clk_hw *hw) +{ + struct clk_branch *br = to_clk_branch(hw); + struct clk_regmap *rclk = to_clk_regmap(hw); + int size, i, val; + + static struct clk_register_data data[] = { + {"CBCR", 0x0}, + }; + + static struct clk_register_data data1[] = { + {"APSS_VOTE", 0x0}, + {"APSS_SLEEP_VOTE", 0x4}, + }; + + size = ARRAY_SIZE(data); + + for (i = 0; i < size; i++) { + regmap_read(br->clkr.regmap, br->halt_reg + data[i].offset, + &val); + seq_printf(f, "%20s: 0x%.8x\n", data[i].name, val); + } + + if ((br->halt_check & BRANCH_HALT_VOTED) && + !(br->halt_check & BRANCH_VOTED)) { + if (rclk->enable_reg) { + size = ARRAY_SIZE(data1); + for (i = 0; i < size; i++) { + regmap_read(br->clkr.regmap, rclk->enable_reg + + data1[i].offset, &val); + seq_printf(f, "%20s: 0x%.8x\n", + data1[i].name, val); + } + } + } +} + +static struct clk_regmap_ops clk_branch2_regmap_ops = { + .list_registers = clk_branch2_list_registers, +}; + +static void clk_branch2_init(struct clk_hw *hw) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + + if (!rclk->ops) + rclk->ops = &clk_branch2_regmap_ops; +} + const struct clk_ops clk_branch2_ops = { .enable = clk_branch2_enable, .disable = clk_branch2_disable, .is_enabled = clk_is_enabled_regmap, - .debug_init = clk_debug_measure_add, + .init = clk_branch2_init, + .debug_init = clk_branch_debug_init, }; EXPORT_SYMBOL_GPL(clk_branch2_ops); const struct clk_ops clk_branch2_aon_ops = { .enable = clk_branch2_enable, .is_enabled = clk_is_enabled_regmap, + .init = clk_branch2_init, + .debug_init = clk_branch_debug_init, }; EXPORT_SYMBOL_GPL(clk_branch2_aon_ops); diff --git a/drivers/clk/qcom/clk-debug.c b/drivers/clk/qcom/clk-debug.c index 686ca87a9f3e..a63429096d71 100644 --- a/drivers/clk/qcom/clk-debug.c +++ b/drivers/clk/qcom/clk-debug.c @@ -386,3 +386,177 @@ int map_debug_bases(struct platform_device *pdev, const char *base, return 0; } EXPORT_SYMBOL(map_debug_bases); + +static void clock_print_rate_max_by_level(struct clk_hw *hw, + struct seq_file *s, int level) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + struct clk_vdd_class_data *vdd_data = &rclk->vdd_data; + struct clk_vdd_class *vdd_class = vdd_data->vdd_class; + int off, i, vdd_level, nregs = vdd_class->num_regulators; + unsigned long rate; + + rate = clk_get_rate(hw->clk); + vdd_level = clk_find_vdd_level(hw, vdd_data, rate); + + seq_printf(s, "%2s%10lu", vdd_level == level ? "[" : "", + vdd_data->rate_max[level]); + + for (i = 0; i < nregs; i++) { + off = nregs*level + i; + if (vdd_class->vdd_uv) + seq_printf(s, "%10u", vdd_class->vdd_uv[off]); + } + + if (vdd_level == level) + seq_puts(s, "]"); + + seq_puts(s, "\n"); +} + +static int rate_max_show(struct seq_file *s, void *unused) +{ + struct clk_hw *hw = s->private; + struct clk_regmap *rclk = to_clk_regmap(hw); + struct clk_vdd_class_data *vdd_data = &rclk->vdd_data; + struct clk_vdd_class *vdd_class = vdd_data->vdd_class; + int level = 0, i, nregs = vdd_class->num_regulators, vdd_level; + char reg_name[10]; + unsigned long rate; + + rate = clk_get_rate(hw->clk); + vdd_level = clk_find_vdd_level(hw, vdd_data, rate); + + if (vdd_level < 0) { + seq_printf(s, "could not find_vdd_level for %s, %ld\n", + clk_hw_get_name(hw), rate); + return 0; + } + + seq_printf(s, "%12s", ""); + for (i = 0; i < nregs; i++) { + snprintf(reg_name, ARRAY_SIZE(reg_name), "reg %d", i); + seq_printf(s, "%10s", reg_name); + } + + seq_printf(s, "\n%12s", "freq"); + for (i = 0; i < nregs; i++) + seq_printf(s, "%10s", "uV"); + + seq_puts(s, "\n"); + + for (level = 0; level < vdd_data->num_rate_max; level++) + clock_print_rate_max_by_level(hw, s, level); + + return 0; +} + +static int rate_max_open(struct inode *inode, struct file *file) +{ + return single_open(file, rate_max_show, inode->i_private); +} + +static const struct file_operations rate_max_fops = { + .open = rate_max_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int list_rates_show(struct seq_file *s, void *unused) +{ + struct clk_hw *hw = s->private; + struct clk_regmap *rclk = to_clk_regmap(hw); + struct clk_vdd_class_data *vdd_data = &rclk->vdd_data; + struct clk_vdd_class *vdd_class = vdd_data->vdd_class; + int i = 0, level; + unsigned long rate, rate_max = 0; + + /* Find max frequency supported within voltage constraints. */ + if (!vdd_class) { + rate_max = ULONG_MAX; + } else { + for (level = 0; level < vdd_data->num_rate_max; level++) + if (vdd_data->rate_max[level]) + rate_max = vdd_data->rate_max[level]; + } + + /* + * List supported frequencies <= rate_max. Higher frequencies may + * appear in the frequency table, but are not valid and should not + * be listed. + */ + while (!IS_ERR_VALUE(rate = rclk->ops->list_rate(hw, i++, rate_max))) { + if (rate <= 0) + break; + seq_printf(s, "%lu\n", rate); + } + + return 0; +} + +static int list_rates_open(struct inode *inode, struct file *file) +{ + return single_open(file, list_rates_show, inode->i_private); +} + +static const struct file_operations list_rates_fops = { + .open = list_rates_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void clk_debug_print_hw(struct clk_hw *hw, struct seq_file *f) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + + if (IS_ERR_OR_NULL(hw)) + return; + + clk_debug_print_hw(clk_hw_get_parent(hw), f); + + seq_printf(f, "%s\n", clk_hw_get_name(hw)); + + if (rclk->ops && rclk->ops->list_registers) + rclk->ops->list_registers(f, hw); +} + +static int print_hw_show(struct seq_file *m, void *unused) +{ + struct clk_hw *hw = m->private; + + clk_debug_print_hw(hw, m); + + return 0; +} + +static int print_hw_open(struct inode *inode, struct file *file) +{ + return single_open(file, print_hw_show, inode->i_private); +} + +static const struct file_operations clock_print_hw_fops = { + .open = print_hw_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +void clk_common_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + + if (rclk->vdd_data.vdd_class) + debugfs_create_file("clk_rate_max", 0444, dentry, hw, + &rate_max_fops); + + if (rclk->ops && rclk->ops->list_rate) + debugfs_create_file("clk_list_rates", 0444, dentry, hw, + &list_rates_fops); + + debugfs_create_file("clk_print_regs", 0444, dentry, hw, + &clock_print_hw_fops); + +}; + diff --git a/drivers/clk/qcom/clk-debug.h b/drivers/clk/qcom/clk-debug.h index 84fb4aeec41c..e37e8628ae8c 100644 --- a/drivers/clk/qcom/clk-debug.h +++ b/drivers/clk/qcom/clk-debug.h @@ -90,4 +90,6 @@ void clk_debug_measure_add(struct clk_hw *hw, struct dentry *dentry); int map_debug_bases(struct platform_device *pdev, const char *base, struct clk_debug_mux *mux); +void clk_common_debug_init(struct clk_hw *hw, struct dentry *dentry); + #endif diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index a19cae9e2c1d..2e42e627af4a 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -20,6 +20,7 @@ #include "clk-rcg.h" #include "common.h" +#include "clk-debug.h" #define CMD_REG 0x0 #define CMD_UPDATE BIT(0) @@ -444,6 +445,61 @@ static int clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f) return update_config(rcg); } +static void clk_rcg2_list_registers(struct seq_file *f, struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + int i = 0, size = 0, val; + + static struct clk_register_data data[] = { + {"CMD_RCGR", 0x0}, + {"CFG_RCGR", 0x4}, + }; + + static struct clk_register_data data1[] = { + {"CMD_RCGR", 0x0}, + {"CFG_RCGR", 0x4}, + {"M_VAL", 0x8}, + {"N_VAL", 0xC}, + {"D_VAL", 0x10}, + }; + + if (rcg->mnd_width) { + size = ARRAY_SIZE(data1); + for (i = 0; i < size; i++) { + regmap_read(rcg->clkr.regmap, (rcg->cmd_rcgr + + data1[i].offset), &val); + seq_printf(f, "%20s: 0x%.8x\n", data1[i].name, val); + } + } else { + size = ARRAY_SIZE(data); + for (i = 0; i < size; i++) { + regmap_read(rcg->clkr.regmap, (rcg->cmd_rcgr + + data[i].offset), &val); + seq_printf(f, "%20s: 0x%.8x\n", data[i].name, val); + } + } +} + +/* Return the nth supported frequency for a given clock. */ +static long clk_rcg2_list_rate(struct clk_hw *hw, unsigned int n, + unsigned long fmax) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + const struct freq_tbl *f = rcg->freq_tbl; + size_t freq_tbl_size = 0; + + if (!f) + return -ENXIO; + + for (; f->freq; f++) + freq_tbl_size++; + + if (n > freq_tbl_size - 1) + return -EINVAL; + + return (rcg->freq_tbl + n)->freq; +} + static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate, enum freq_policy policy) { @@ -628,6 +684,19 @@ static void clk_rcg2_disable(struct clk_hw *hw) clk_rcg2_clear_force_enable(hw); } +static struct clk_regmap_ops clk_rcg2_regmap_ops = { + .list_rate = clk_rcg2_list_rate, + .list_registers = clk_rcg2_list_registers, +}; + +static void clk_rcg2_init(struct clk_hw *hw) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + + if (!rclk->ops) + rclk->ops = &clk_rcg2_regmap_ops; +} + const struct clk_ops clk_rcg2_ops = { .prepare = clk_prepare_regmap, .unprepare = clk_unprepare_regmap, @@ -642,6 +711,11 @@ const struct clk_ops clk_rcg2_ops = { .determine_rate = clk_rcg2_determine_rate, .set_rate = clk_rcg2_set_rate, .set_rate_and_parent = clk_rcg2_set_rate_and_parent, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_rcg2_ops); @@ -657,6 +731,11 @@ const struct clk_ops clk_rcg2_floor_ops = { .determine_rate = clk_rcg2_determine_floor_rate, .set_rate = clk_rcg2_set_floor_rate, .set_rate_and_parent = clk_rcg2_set_floor_rate_and_parent, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_rcg2_floor_ops); @@ -790,6 +869,11 @@ const struct clk_ops clk_edp_pixel_ops = { .set_rate = clk_edp_pixel_set_rate, .set_rate_and_parent = clk_edp_pixel_set_rate_and_parent, .determine_rate = clk_edp_pixel_determine_rate, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_edp_pixel_ops); @@ -854,6 +938,11 @@ const struct clk_ops clk_byte_ops = { .set_rate = clk_byte_set_rate, .set_rate_and_parent = clk_byte_set_rate_and_parent, .determine_rate = clk_byte_determine_rate, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_byte_ops); @@ -928,6 +1017,11 @@ const struct clk_ops clk_byte2_ops = { .set_rate = clk_byte2_set_rate, .set_rate_and_parent = clk_byte2_set_rate_and_parent, .determine_rate = clk_byte2_determine_rate, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_byte2_ops); @@ -1022,6 +1116,11 @@ const struct clk_ops clk_pixel_ops = { .set_rate = clk_pixel_set_rate, .set_rate_and_parent = clk_pixel_set_rate_and_parent, .determine_rate = clk_pixel_determine_rate, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_pixel_ops); @@ -1108,6 +1207,11 @@ const struct clk_ops clk_dp_ops = { .set_rate = clk_dp_set_rate, .set_rate_and_parent = clk_dp_set_rate_and_parent, .determine_rate = clk_dp_determine_rate, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL(clk_dp_ops); @@ -1203,6 +1307,11 @@ const struct clk_ops clk_gfx3d_ops = { .set_rate = clk_gfx3d_set_rate, .set_rate_and_parent = clk_gfx3d_set_rate_and_parent, .determine_rate = clk_gfx3d_determine_rate, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_gfx3d_ops); @@ -1314,6 +1423,11 @@ const struct clk_ops clk_rcg2_shared_ops = { .determine_rate = clk_rcg2_determine_rate, .set_rate = clk_rcg2_shared_set_rate, .set_rate_and_parent = clk_rcg2_shared_set_rate_and_parent, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL_GPL(clk_rcg2_shared_ops); @@ -1458,6 +1572,11 @@ static const struct clk_ops clk_rcg2_dfs_ops = { .get_parent = clk_rcg2_get_parent, .determine_rate = clk_rcg2_dfs_determine_rate, .recalc_rate = clk_rcg2_dfs_recalc_rate, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; static int clk_rcg2_enable_dfs(const struct clk_rcg_dfs_data *data, @@ -1601,5 +1720,10 @@ const struct clk_ops clk_rcg2_dependent_ops = { .determine_rate = clk_rcg2_dependent_determine_rate, .set_rate = clk_rcg2_dependent_set_rate, .set_rate_and_parent = clk_rcg2_dependent_set_rate_and_parent, + .init = clk_rcg2_init, + .debug_init = clk_common_debug_init, +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + .list_rate_vdd_level = clk_list_rate_vdd_level, +#endif }; EXPORT_SYMBOL(clk_rcg2_dependent_ops); diff --git a/drivers/clk/qcom/clk-regmap.h b/drivers/clk/qcom/clk-regmap.h index 9efb74b2a6cb..30f1ae366f9e 100644 --- a/drivers/clk/qcom/clk-regmap.h +++ b/drivers/clk/qcom/clk-regmap.h @@ -5,10 +5,28 @@ #define __QCOM_CLK_REGMAP_H__ #include +#include #include "vdd-class.h" struct regmap; +/** + * struct clk_regmap_ops - Operations for clk_regmap. + * + * @list_registers: Queries the hardware to get the current register contents. + * This callback is optional. + * + * @list_rate: On success, return the nth supported frequency for a given + * clock that is below rate_max. Return -ENXIO in case there is + * no frequency table. + */ +struct clk_regmap_ops { + void (*list_registers)(struct seq_file *f, + struct clk_hw *hw); + long (*list_rate)(struct clk_hw *hw, unsigned int n, + unsigned long rate_max); +}; + /** * struct clk_regmap - regmap supporting clock * @hw: handle between common and hardware-specific interfaces @@ -19,7 +37,9 @@ struct regmap; * @enable_is_inverted: flag to indicate set enable_mask bits to disable * when using clock_enable_regmap and friends APIs. * @vdd_data: struct containing vdd-class data for this clock + * @ops: operations this clk_regmap supports */ + struct clk_regmap { struct clk_hw hw; struct clk_hw *dependent_hw; @@ -28,6 +48,7 @@ struct clk_regmap { unsigned int enable_mask; bool enable_is_inverted; struct clk_vdd_class_data vdd_data; + struct clk_regmap_ops *ops; }; #define to_clk_regmap(_hw) container_of(_hw, struct clk_regmap, hw) @@ -42,4 +63,9 @@ int clk_post_change_regmap(struct clk_hw *hw, unsigned long old_rate, unsigned long cur_rate); int devm_clk_register_regmap(struct device *dev, struct clk_regmap *rclk); +struct clk_register_data { + char *name; + u32 offset; +}; + #endif diff --git a/drivers/clk/qcom/vdd-class.c b/drivers/clk/qcom/vdd-class.c index fcef07077d4b..f2bcdb08a062 100644 --- a/drivers/clk/qcom/vdd-class.c +++ b/drivers/clk/qcom/vdd-class.c @@ -6,6 +6,7 @@ #include #include "vdd-class.h" +#include "clk-regmap.h" /* * Aggregate the vdd_class level votes and call regulator framework functions @@ -169,3 +170,15 @@ int clk_find_vdd_level(struct clk_hw *hw, return level; } EXPORT_SYMBOL(clk_find_vdd_level); + +int clk_list_rate_vdd_level(struct clk_hw *hw, unsigned int rate) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + struct clk_vdd_class_data *vdd_data = &rclk->vdd_data; + + if (!vdd_data->vdd_class) + return 0; + + return clk_find_vdd_level(hw, vdd_data, rate); +} + diff --git a/drivers/clk/qcom/vdd-class.h b/drivers/clk/qcom/vdd-class.h index 9b2b884debdb..a7b8d7b06b34 100644 --- a/drivers/clk/qcom/vdd-class.h +++ b/drivers/clk/qcom/vdd-class.h @@ -66,5 +66,6 @@ int clk_find_vdd_level(struct clk_hw *hw, struct clk_vdd_class_data *vdd_data, unsigned long rate); int clk_vote_vdd_level(struct clk_vdd_class_data *vdd_class, int level); int clk_unvote_vdd_level(struct clk_vdd_class_data *vdd_class, int level); +int clk_list_rate_vdd_level(struct clk_hw *hw, unsigned int rate); #endif diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 0cbcba1940f4..2846eeda3e92 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -208,6 +208,10 @@ struct clk_duty { * requirements that were needed while the clock and its tree * was changing states. Returns 0 on success, -EERROR otherwise. * + * @list_rate_vdd_level: Queries the required voltage level for the given rate. + * The return value may not represent an exact voltage and instead + * may be an abstract index or voltage "corner". + * * The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow * implementations to split any work between atomic (enable) and sleepable * (prepare) contexts. If enabling a clock requires code that might sleep, @@ -259,6 +263,10 @@ struct clk_ops { int (*post_rate_change)(struct clk_hw *hw, unsigned long old_rate, unsigned long rate); +#ifdef CONFIG_COMMON_CLK_QCOM_DEBUG + int (*list_rate_vdd_level)(struct clk_hw *hw, + unsigned int rate); +#endif }; /** diff --git a/include/trace/events/clk.h b/include/trace/events/clk.h index cb1aea25c199..bb74d3bba8c4 100644 --- a/include/trace/events/clk.h +++ b/include/trace/events/clk.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. */ #undef TRACE_SYSTEM #define TRACE_SYSTEM clk @@ -220,6 +220,42 @@ DEFINE_EVENT(clk_duty_cycle, clk_set_duty_cycle_complete, TP_ARGS(core, duty) ); +DECLARE_EVENT_CLASS(clk_state_dump, + + TP_PROTO(const char *name, unsigned int prepare_count, + unsigned int enable_count, unsigned long rate, unsigned int vdd_level), + + TP_ARGS(name, prepare_count, enable_count, rate, vdd_level), + + TP_STRUCT__entry( + __string(name, name) + __field(unsigned int, prepare_count) + __field(unsigned int, enable_count) + __field(unsigned long, rate) + __field(unsigned int, vdd_level) + ), + + TP_fast_assign( + __assign_str(name, name); + __entry->prepare_count = prepare_count; + __entry->enable_count = enable_count; + __entry->rate = rate; + __entry->vdd_level = vdd_level; + ), + + TP_printk("%s\tprepare:enable cnt [%u:%u]\trate: vdd_level [%lu:%u]", + __get_str(name), __entry->prepare_count, __entry->enable_count, + __entry->rate, __entry->vdd_level) +); + +DEFINE_EVENT(clk_state_dump, clk_state, + + TP_PROTO(const char *name, unsigned int prepare_count, + unsigned int enable_count, unsigned long rate, unsigned int vdd_level), + + TP_ARGS(name, prepare_count, enable_count, rate, vdd_level) +); + #endif /* _TRACE_CLK_H */ /* This part must be outside protection */