Files
kernel-tenderloin-3.0/arch/arm/mach-msm/clock-debug.c
Stephen Boyd 3bbf3462ee msm: clock: Support clk_prepare/unprepare()
Fill in the boiler plate prepare()/unprepare() code. Add a
prepare count and a prepare lock to protect the prepare/unprepare
paths. Also handle the parent/child relationship similarly to how
enable/disable is done.

In particular, WARN() whenever a driver calls clk_enable() before
calling clk_prepare() and whenever a driver calls clk_unprepare()
before calling clk_disable(). This should catch a few bugs even
though it's technically not SMP safe if an enable call is racing
with an unprepare call for example.

We also mark PLLs and XOs as already warned since those are
mostly internal clocks that driver authors aren't aware of. The
markings will be removed when the local clock driver is updated
to handle prepare/unprepare in another patch.

Change-Id: I41dd39d27a279f3477c6e1cef292ac5308d65243
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
2012-03-13 18:05:21 -07:00

291 lines
6.6 KiB
C

/*
* Copyright (C) 2007 Google, Inc.
* Copyright (c) 2007-2012, Code Aurora Forum. 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/clk.h>
#include <linux/list.h>
#include <linux/clkdev.h>
#include "clock.h"
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 failed (%d)\n", 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;
int ret, is_hw_gated;
/* Check to see if the clock is in hardware gating mode */
if (clock->flags & CLKFLAG_HWCG)
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);
*val = clk_get_rate(measure);
/* Reenable hwgating if it was disabled */
if (is_hw_gated && clock->count)
clock->ops->enable_hwcg(clock);
}
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;
*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;
*val = !!(clock->flags & CLKFLAG_HWCG);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(clock_hwcg_fops, clock_debug_hwcg_get,
NULL, "%llu\n");
static struct dentry *debugfs_base;
static u32 debug_suspend;
static struct clk_lookup *msm_clocks;
static size_t num_msm_clocks;
int __init clock_debug_init(struct clock_init_data *data)
{
int ret = 0;
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;
}
msm_clocks = data->table;
num_msm_clocks = data->size;
measure = clk_get_sys("debug", "measure");
if (IS_ERR(measure)) {
ret = PTR_ERR(measure);
measure = NULL;
}
return ret;
}
static int clock_debug_print_clock(struct clk *c)
{
size_t ln = 0;
char s[128];
if (!c || !c->count)
return 0;
ln += snprintf(s, sizeof(s), "\t%s", c->dbg_name);
while (ln < sizeof(s) && (c = clk_get_parent(c)))
ln += snprintf(s + ln, sizeof(s) - ln, " -> %s", c->dbg_name);
pr_info("%s\n", s);
return 1;
}
void clock_debug_print_enabled(void)
{
unsigned i;
int cnt = 0;
if (likely(!debug_suspend))
return;
pr_info("Enabled clocks:\n");
for (i = 0; i < num_msm_clocks; i++)
cnt += clock_debug_print_clock(msm_clocks[i].clk);
if (cnt)
pr_info("Enabled clock count: %d\n", cnt);
else
pr_info("No clocks enabled.\n");
}
static int list_rates_show(struct seq_file *m, void *unused)
{
struct clk *clock = m->private;
int rate, level, fmax = 0, i = 0;
/* Find max frequency supported within voltage constraints. */
if (!clock->vdd_class) {
fmax = INT_MAX;
} else {
for (level = 0; level < ARRAY_SIZE(clock->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 ((rate = clock->ops->list_rate(clock, i++)) >= 0) {
if (rate <= fmax)
seq_printf(m, "%u\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,
};
int __init 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;
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 (measure &&
!clk_set_parent(measure, clock) &&
!debugfs_create_file("measure", S_IRUGO, clk_dir, clock,
&clock_measure_fops))
goto error;
if (clock->ops->list_rate)
if (!debugfs_create_file("list_rates",
S_IRUGO, clk_dir, clock, &list_rates_fops))
goto error;
return 0;
error:
debugfs_remove_recursive(clk_dir);
return -ENOMEM;
}