mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-09 04:10:18 +09:00
clk: add/modify debugfs support for clocks
Update clock debugfs to support the below functionalities. - Allow enable/disable a clock. - Allow set_rate on a clock. - Display available parent of a clock. - Allow set_parent on a clock. - Display the list of enabled_clocks along with prepare_count, enable_count and rate. Change-Id: Ib67b3a3409c9e7d8adb710bb524f54f543abf712 Signed-off-by: Taniya Das <tdas@codeaurora.org> Signed-off-by: Tao Huang <huangtao@rock-chips.com> Signed-off-by: Finley Xiao <finley.xiao@rock-chips.com>
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <linux/clkdev.h>
|
#include <linux/clkdev.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
|
||||||
#include "clk.h"
|
#include "clk.h"
|
||||||
|
|
||||||
@@ -2141,6 +2142,210 @@ static const struct file_operations clk_dump_fops = {
|
|||||||
.release = single_release,
|
.release = single_release,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 = core->hw->core->rate;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_SIMPLE_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get,
|
||||||
|
clock_debug_rate_set, "%llu\n");
|
||||||
|
|
||||||
|
static int clock_available_parent_show(struct seq_file *s, void *data)
|
||||||
|
{
|
||||||
|
struct clk_core *core = (struct clk_core *)s->private;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < core->num_parents; i++) {
|
||||||
|
if (!core->parents[i])
|
||||||
|
continue;
|
||||||
|
seq_printf(s, "%s ", core->parents[i]->name);
|
||||||
|
}
|
||||||
|
seq_puts(s, "\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clock_available_parent_open(struct inode *inode, struct file *file)
|
||||||
|
{
|
||||||
|
return single_open(file, clock_available_parent_show, inode->i_private);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct file_operations clock_available_parent_fops = {
|
||||||
|
.open = clock_available_parent_open,
|
||||||
|
.read = seq_read,
|
||||||
|
.llseek = seq_lseek,
|
||||||
|
.release = single_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
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->parent;
|
||||||
|
|
||||||
|
snprintf(name, sizeof(name), "%s\n", p ? p->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 *buf,
|
||||||
|
size_t cnt, loff_t *ppos)
|
||||||
|
{
|
||||||
|
char temp[256] = {0};
|
||||||
|
char name[256] = {0};
|
||||||
|
struct clk_core *core = filp->private_data;
|
||||||
|
unsigned int ret, i;
|
||||||
|
|
||||||
|
if (copy_from_user(temp, buf, cnt))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
ret = sscanf(temp, "%s", name);
|
||||||
|
if (ret != 1)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
for (i = 0; i < core->num_parents; i++) {
|
||||||
|
if (!core->parents[i])
|
||||||
|
continue;
|
||||||
|
if (!strcmp(core->parents[i]->name, name)) {
|
||||||
|
if (core->parents[i] != core->parent)
|
||||||
|
clk_core_set_parent(core, core->parents[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct file_operations clock_parent_fops = {
|
||||||
|
.open = simple_open,
|
||||||
|
.read = clock_parent_read,
|
||||||
|
.write = clock_parent_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
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_SIMPLE_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get,
|
||||||
|
clock_debug_enable_set, "%lld\n");
|
||||||
|
|
||||||
|
#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_core *c, struct seq_file *s)
|
||||||
|
{
|
||||||
|
char *start = "";
|
||||||
|
struct clk *clk;
|
||||||
|
|
||||||
|
if (!c || !c->prepare_count)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
clk = c->hw->clk;
|
||||||
|
|
||||||
|
clock_debug_output(s, 0, "\t");
|
||||||
|
|
||||||
|
do {
|
||||||
|
clock_debug_output(s, 1, "%s%s:%u:%u [%ld]", start,
|
||||||
|
clk->core->name,
|
||||||
|
clk->core->prepare_count,
|
||||||
|
clk->core->enable_count,
|
||||||
|
clk->core->rate);
|
||||||
|
start = " -> ";
|
||||||
|
} while ((clk = clk_get_parent(clk)));
|
||||||
|
|
||||||
|
clock_debug_output(s, 1, "\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, 0, "Enabled clocks:\n");
|
||||||
|
|
||||||
|
mutex_lock(&clk_debug_lock);
|
||||||
|
|
||||||
|
hlist_for_each_entry(core, &clk_debug_list, debug_node)
|
||||||
|
cnt += clock_debug_print_clock(core, s);
|
||||||
|
|
||||||
|
mutex_unlock(&clk_debug_lock);
|
||||||
|
|
||||||
|
if (cnt)
|
||||||
|
clock_debug_output(s, 0, "Enabled clock count: %d\n", cnt);
|
||||||
|
else
|
||||||
|
clock_debug_output(s, 0, "No clocks enabled.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int enabled_clocks_show(struct seq_file *s, void *unused)
|
||||||
|
{
|
||||||
|
clock_debug_print_enabled_clocks(s);
|
||||||
|
|
||||||
|
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 int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
|
static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
|
||||||
{
|
{
|
||||||
struct dentry *d;
|
struct dentry *d;
|
||||||
@@ -2157,8 +2362,8 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
|
|||||||
|
|
||||||
core->dentry = d;
|
core->dentry = d;
|
||||||
|
|
||||||
d = debugfs_create_u32("clk_rate", S_IRUGO, core->dentry,
|
d = debugfs_create_file("clk_rate", S_IRUGO | S_IWUSR, core->dentry,
|
||||||
(u32 *)&core->rate);
|
core, &clock_rate_fops);
|
||||||
if (!d)
|
if (!d)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
|
||||||
@@ -2182,8 +2387,8 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
|
|||||||
if (!d)
|
if (!d)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
|
||||||
d = debugfs_create_u32("clk_enable_count", S_IRUGO, core->dentry,
|
d = debugfs_create_file("clk_enable_count", S_IRUGO | S_IWUSR,
|
||||||
(u32 *)&core->enable_count);
|
core->dentry, core, &clock_enable_fops);
|
||||||
if (!d)
|
if (!d)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
|
||||||
@@ -2192,6 +2397,16 @@ static int clk_debug_create_one(struct clk_core *core, struct dentry *pdentry)
|
|||||||
if (!d)
|
if (!d)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
|
||||||
|
d = debugfs_create_file("clk_available_parent", S_IRUGO, core->dentry,
|
||||||
|
core, &clock_available_parent_fops);
|
||||||
|
if (!d)
|
||||||
|
goto err_out;
|
||||||
|
|
||||||
|
d = debugfs_create_file("clk_parent", S_IRUGO | S_IWUSR, core->dentry,
|
||||||
|
core, &clock_parent_fops);
|
||||||
|
if (!d)
|
||||||
|
goto err_out;
|
||||||
|
|
||||||
if (core->ops->debug_init) {
|
if (core->ops->debug_init) {
|
||||||
ret = core->ops->debug_init(core->hw, core->dentry);
|
ret = core->ops->debug_init(core->hw, core->dentry);
|
||||||
if (ret)
|
if (ret)
|
||||||
@@ -2302,6 +2517,11 @@ static int __init clk_debug_init(void)
|
|||||||
if (!d)
|
if (!d)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
d = debugfs_create_file("clk_enabled_list", S_IRUGO, rootdir,
|
||||||
|
&clk_debug_list, &clk_enabled_list_fops);
|
||||||
|
if (!d)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
mutex_lock(&clk_debug_lock);
|
mutex_lock(&clk_debug_lock);
|
||||||
hlist_for_each_entry(core, &clk_debug_list, debug_node)
|
hlist_for_each_entry(core, &clk_debug_list, debug_node)
|
||||||
clk_debug_create_one(core, rootdir);
|
clk_debug_create_one(core, rootdir);
|
||||||
|
|||||||
Reference in New Issue
Block a user