/* * Copyright (C) 2007 Google, Inc. * Copyright (c) 2007-2014, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clock.h" static LIST_HEAD(clk_list); static DEFINE_MUTEX(clk_list_lock); static struct dentry *debugfs_base; static u32 debug_suspend; static int clock_debug_rate_set(void *data, u64 val) { struct clk *clock = data; int ret; /* Only increases to max rate will succeed, but that's actually good * for debugging purposes so we don't check for error. */ if (clock->flags & CLKFLAG_MAX) clk_set_max_rate(clock, val); ret = clk_set_rate(clock, val); if (ret) pr_err("clk_set_rate(%s, %lu) failed (%d)\n", clock->dbg_name, (unsigned long)val, ret); return ret; } static int clock_debug_rate_get(void *data, u64 *val) { struct clk *clock = data; *val = clk_get_rate(clock); return 0; } DEFINE_SIMPLE_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get, clock_debug_rate_set, "%llu\n"); static struct clk *measure; static int clock_debug_measure_get(void *data, u64 *val) { struct clk *clock = data, *par; int ret, is_hw_gated; unsigned long meas_rate, sw_rate; /* Check to see if the clock is in hardware gating mode */ if (clock->ops->in_hwcg_mode) is_hw_gated = clock->ops->in_hwcg_mode(clock); else is_hw_gated = 0; ret = clk_set_parent(measure, clock); if (!ret) { /* * Disable hw gating to get accurate rate measurements. Only do * this if the clock is explictly enabled by software. This * allows us to detect errors where clocks are on even though * software is not requesting them to be on due to broken * hardware gating signals. */ if (is_hw_gated && clock->count) clock->ops->disable_hwcg(clock); par = measure; while (par && par != clock) { if (par->ops->enable) par->ops->enable(par); par = par->parent; } *val = clk_get_rate(measure); /* Reenable hwgating if it was disabled */ if (is_hw_gated && clock->count) clock->ops->enable_hwcg(clock); } /* * If there's a divider on the path from the clock output to the * measurement circuitry, account for it by dividing the original clock * rate with the rate set on the parent of the measure clock. */ meas_rate = clk_get_rate(clock); sw_rate = clk_get_rate(measure->parent); if (sw_rate && meas_rate >= (sw_rate * 2)) *val *= DIV_ROUND_CLOSEST(meas_rate, sw_rate); return ret; } DEFINE_SIMPLE_ATTRIBUTE(clock_measure_fops, clock_debug_measure_get, NULL, "%lld\n"); static int clock_debug_enable_set(void *data, u64 val) { struct clk *clock = data; int rc = 0; if (val) rc = clk_prepare_enable(clock); else clk_disable_unprepare(clock); return rc; } static int clock_debug_enable_get(void *data, u64 *val) { struct clk *clock = data; int enabled; if (clock->ops->is_enabled) enabled = clock->ops->is_enabled(clock); else enabled = !!(clock->count); *val = enabled; return 0; } DEFINE_SIMPLE_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get, clock_debug_enable_set, "%lld\n"); static int clock_debug_local_get(void *data, u64 *val) { struct clk *clock = data; if (!clock->ops->is_local) *val = true; else *val = clock->ops->is_local(clock); return 0; } DEFINE_SIMPLE_ATTRIBUTE(clock_local_fops, clock_debug_local_get, NULL, "%llu\n"); static int clock_debug_hwcg_get(void *data, u64 *val) { struct clk *clock = data; if (clock->ops->in_hwcg_mode) *val = !!clock->ops->in_hwcg_mode(clock); else *val = 0; return 0; } DEFINE_SIMPLE_ATTRIBUTE(clock_hwcg_fops, clock_debug_hwcg_get, NULL, "%llu\n"); static void clock_print_fmax_by_level(struct seq_file *m, int level) { struct clk *clock = m->private; struct clk_vdd_class *vdd_class = clock->vdd_class; int off, i, vdd_level, nregs = vdd_class->num_regulators; vdd_level = find_vdd_level(clock, clock->rate); seq_printf(m, "%2s%10lu", vdd_level == level ? "[" : "", clock->fmax[level]); for (i = 0; i < nregs; i++) { off = nregs*level + i; if (vdd_class->vdd_uv) seq_printf(m, "%10u", vdd_class->vdd_uv[off]); if (vdd_class->vdd_ua) seq_printf(m, "%10u", vdd_class->vdd_ua[off]); } if (vdd_level == level) seq_puts(m, "]"); seq_puts(m, "\n"); } static int fmax_rates_show(struct seq_file *m, void *unused) { struct clk *clock = m->private; struct clk_vdd_class *vdd_class = clock->vdd_class; int level = 0, i, nregs = vdd_class->num_regulators; char reg_name[10]; int vdd_level = find_vdd_level(clock, clock->rate); if (vdd_level < 0) { seq_printf(m, "could not find_vdd_level for %s, %ld\n", clock->dbg_name, clock->rate); return 0; } seq_printf(m, "%12s", ""); for (i = 0; i < nregs; i++) { snprintf(reg_name, ARRAY_SIZE(reg_name), "reg %d", i); seq_printf(m, "%10s", reg_name); if (vdd_class->vdd_ua) seq_printf(m, "%10s", ""); } seq_printf(m, "\n%12s", "freq"); for (i = 0; i < nregs; i++) { seq_printf(m, "%10s", "uV"); if (vdd_class->vdd_ua) seq_printf(m, "%10s", "uA"); } seq_printf(m, "\n"); for (level = 0; level < clock->num_fmax; level++) clock_print_fmax_by_level(m, level); return 0; } static int fmax_rates_open(struct inode *inode, struct file *file) { return single_open(file, fmax_rates_show, inode->i_private); } static const struct file_operations fmax_rates_fops = { .open = fmax_rates_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int orphan_list_show(struct seq_file *m, void *unused) { struct clk *c, *safe; list_for_each_entry_safe(c, safe, &orphan_clk_list, list) seq_printf(m, "%s\n", c->dbg_name); return 0; } static int orphan_list_open(struct inode *inode, struct file *file) { return single_open(file, orphan_list_show, inode->i_private); } static const struct file_operations orphan_list_fops = { .open = orphan_list_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; #define clock_debug_output(m, c, fmt, ...) \ do { \ if (m) \ seq_printf(m, fmt, ##__VA_ARGS__); \ else if (c) \ pr_cont(fmt, ##__VA_ARGS__); \ else \ pr_info(fmt, ##__VA_ARGS__); \ } while (0) static int clock_debug_print_clock(struct clk *c, struct seq_file *m) { char *start = ""; if (!c || !c->prepare_count) return 0; clock_debug_output(m, 0, "\t"); do { if (c->vdd_class) clock_debug_output(m, 1, "%s%s:%u:%u [%ld, %d]", start, c->dbg_name, c->prepare_count, c->count, c->rate, find_vdd_level(c, c->rate)); else clock_debug_output(m, 1, "%s%s:%u:%u [%ld]", start, c->dbg_name, c->prepare_count, c->count, c->rate); start = " -> "; } while ((c = clk_get_parent(c))); clock_debug_output(m, 1, "\n"); return 1; } /** * clock_debug_print_enabled_clocks() - Print names of enabled clocks * */ static void clock_debug_print_enabled_clocks(struct seq_file *m) { struct clk *c; int cnt = 0; if (!mutex_trylock(&clk_list_lock)) { pr_err("clock-debug: Clocks are being registered. Cannot print clock state now.\n"); return; } clock_debug_output(m, 0, "Enabled clocks:\n"); list_for_each_entry(c, &clk_list, list) { cnt += clock_debug_print_clock(c, m); } mutex_unlock(&clk_list_lock); if (cnt) clock_debug_output(m, 0, "Enabled clock count: %d\n", cnt); else clock_debug_output(m, 0, "No clocks enabled.\n"); } static int enabled_clocks_show(struct seq_file *m, void *unused) { clock_debug_print_enabled_clocks(m); 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 enabled_clocks_fops = { .open = enabled_clocks_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int trace_clocks_show(struct seq_file *m, void *unused) { struct clk *c; int total_cnt = 0; if (!mutex_trylock(&clk_list_lock)) { pr_err("trace_clocks: Clocks are being registered. Cannot trace clock state now.\n"); return 1; } list_for_each_entry(c, &clk_list, list) { trace_clock_state(c->dbg_name, c->prepare_count, c->count, c->rate); total_cnt++; } mutex_unlock(&clk_list_lock); clock_debug_output(m, 0, "Total clock count: %d\n", total_cnt); return 0; } static int trace_clocks_open(struct inode *inode, struct file *file) { return single_open(file, trace_clocks_show, inode->i_private); } static const struct file_operations trace_clocks_fops = { .open = trace_clocks_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int list_rates_show(struct seq_file *m, void *unused) { struct clk *clock = m->private; int level, i = 0; unsigned long rate, fmax = 0; /* Find max frequency supported within voltage constraints. */ if (!clock->vdd_class) { fmax = ULONG_MAX; } else { for (level = 0; level < clock->num_fmax; level++) if (clock->fmax[level]) fmax = clock->fmax[level]; } /* * List supported frequencies <= fmax. Higher frequencies may appear in * the frequency table, but are not valid and should not be listed. */ while (!IS_ERR_VALUE(rate = clock->ops->list_rate(clock, i++))) { if (rate <= fmax) seq_printf(m, "%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 ssize_t clock_parent_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos) { struct clk *clock = filp->private_data; struct clk *p = clock->parent; char name[256] = {0}; snprintf(name, sizeof(name), "%s\n", p ? p->dbg_name : "None\n"); return simple_read_from_buffer(ubuf, cnt, ppos, name, strlen(name)); } static ssize_t clock_parent_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { struct clk *clock = filp->private_data; char buf[256]; char *cmp; int ret; struct clk *parent = NULL; cnt = min(cnt, sizeof(buf) - 1); if (copy_from_user(&buf, ubuf, cnt)) return -EFAULT; buf[cnt] = '\0'; cmp = strstrip(buf); mutex_lock(&clk_list_lock); list_for_each_entry(parent, &clk_list, list) { if (!strcmp(cmp, parent->dbg_name)) break; } if (&parent->list == &clk_list) { ret = -EINVAL; goto err; } mutex_unlock(&clk_list_lock); ret = clk_set_parent(clock, parent); if (ret) return ret; return cnt; err: mutex_unlock(&clk_list_lock); return ret; } static const struct file_operations clock_parent_fops = { .open = simple_open, .read = clock_parent_read, .write = clock_parent_write, }; void clk_debug_print_hw(struct clk *clk, struct seq_file *f) { void __iomem *base; struct clk_register_data *regs; u32 i, j, size; if (IS_ERR_OR_NULL(clk)) return; clk_debug_print_hw(clk->parent, f); clock_debug_output(f, false, "%s\n", clk->dbg_name); if (!clk->ops->list_registers) return; j = 0; base = clk->ops->list_registers(clk, j, ®s, &size); while (!IS_ERR(base)) { for (i = 0; i < size; i++) { u32 val = readl_relaxed(base + regs[i].offset); clock_debug_output(f, false, "%20s: 0x%.8x\n", regs[i].name, val); } j++; base = clk->ops->list_registers(clk, j, ®s, &size); } } static int print_hw_show(struct seq_file *m, void *unused) { struct clk *c = m->private; clk_debug_print_hw(c, 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, }; static void clock_measure_add(struct clk *clock) { if (IS_ERR_OR_NULL(measure)) return; if (clk_set_parent(measure, clock)) return; debugfs_create_file("measure", S_IRUGO, clock->clk_dir, clock, &clock_measure_fops); } static int clock_debug_add(struct clk *clock) { char temp[50], *ptr; struct dentry *clk_dir; if (!debugfs_base) return -ENOMEM; strlcpy(temp, clock->dbg_name, ARRAY_SIZE(temp)); for (ptr = temp; *ptr; ptr++) *ptr = tolower(*ptr); clk_dir = debugfs_create_dir(temp, debugfs_base); if (!clk_dir) return -ENOMEM; clock->clk_dir = clk_dir; if (!debugfs_create_file("rate", S_IRUGO | S_IWUSR, clk_dir, clock, &clock_rate_fops)) goto error; if (!debugfs_create_file("enable", S_IRUGO | S_IWUSR, clk_dir, clock, &clock_enable_fops)) goto error; if (!debugfs_create_file("is_local", S_IRUGO, clk_dir, clock, &clock_local_fops)) goto error; if (!debugfs_create_file("has_hw_gating", S_IRUGO, clk_dir, clock, &clock_hwcg_fops)) goto error; if (clock->ops->list_rate) if (!debugfs_create_file("list_rates", S_IRUGO, clk_dir, clock, &list_rates_fops)) goto error; if (clock->vdd_class && !debugfs_create_file("fmax_rates", S_IRUGO, clk_dir, clock, &fmax_rates_fops)) goto error; if (!debugfs_create_file("parent", S_IRUGO, clk_dir, clock, &clock_parent_fops)) goto error; if (!debugfs_create_file("print", S_IRUGO, clk_dir, clock, &clock_print_hw_fops)) goto error; clock_measure_add(clock); return 0; error: debugfs_remove_recursive(clk_dir); return -ENOMEM; } static DEFINE_MUTEX(clk_debug_lock); static int clk_debug_init_once; /** * clock_debug_init() - Initialize clock debugfs * Lock clk_debug_lock before invoking this function. */ static int clock_debug_init(void) { if (clk_debug_init_once) return 0; clk_debug_init_once = 1; debugfs_base = debugfs_create_dir("clk", NULL); if (!debugfs_base) return -ENOMEM; if (!debugfs_create_u32("debug_suspend", S_IRUGO | S_IWUSR, debugfs_base, &debug_suspend)) { debugfs_remove_recursive(debugfs_base); return -ENOMEM; } if (!debugfs_create_file("enabled_clocks", S_IRUGO, debugfs_base, NULL, &enabled_clocks_fops)) return -ENOMEM; if (!debugfs_create_file("orphan_list", S_IRUGO, debugfs_base, NULL, &orphan_list_fops)) return -ENOMEM; if (!debugfs_create_file("trace_clocks", S_IRUGO, debugfs_base, NULL, &trace_clocks_fops)) return -ENOMEM; return 0; } /** * clock_debug_register() - Add additional clocks to clock debugfs hierarchy * @list: List of clocks to create debugfs nodes for */ int clock_debug_register(struct clk *clk) { int ret = 0; struct clk *c; mutex_lock(&clk_list_lock); if (!list_empty(&clk->list)) goto out; ret = clock_debug_init(); if (ret) goto out; if (IS_ERR_OR_NULL(measure)) { if (clk->flags & CLKFLAG_MEASURE) measure = clk; if (!IS_ERR_OR_NULL(measure)) { list_for_each_entry(c, &clk_list, list) clock_measure_add(c); } } list_add_tail(&clk->list, &clk_list); clock_debug_add(clk); out: mutex_unlock(&clk_list_lock); return ret; } /* * Print the names of enabled clocks and their parents if debug_suspend is set */ void clock_debug_print_enabled(void) { if (likely(!debug_suspend)) return; clock_debug_print_enabled_clocks(NULL); }