blob: ef299f6b8bb2584a345b2be35d9800c112ab41d6 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#undef pr_fmt
#define pr_fmt(fmt) "clk-debug: " fmt
static struct clk *debug_clk;
static ssize_t parent_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
int ret;
char clk_name[24];
char input[24];
struct clk *clk;
if (count >= sizeof(input))
return -EINVAL;
if (copy_from_user(input, buffer, count))
return -EFAULT;
input[count] = '\0';
ret = sscanf(input, "%s", clk_name);
if (ret != 1) {
pr_err("wrong usage, try: echo clk_name > clk\n");
return -EINVAL;
}
clk = __clk_lookup(clk_name);
if (!clk)
pr_err("Can't find the clock, have a look in /sys/kernel/debug/clk\n");
if (debug_clk) {
ret = clk_set_parent(debug_clk, clk);
if (ret < 0)
pr_err("failed to set parent for %s\n",
__clk_get_name(debug_clk));
else
pr_info("now %s parent is %s\n",
__clk_get_name(debug_clk),
__clk_get_name(clk_get_parent(debug_clk)));
} else {
pr_err("try: echo clk_name > clk,echo clk_name > parent\n");
}
return count;
}
static ssize_t enable_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
char buf[4];
unsigned int len;
len = sprintf(buf, "%d\n", __clk_is_enabled(debug_clk) ? 1 : 0);
return simple_read_from_buffer(buffer, count, ppos, buf, len);
}
static ssize_t enable_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
int ret;
char *input;
unsigned int enable;
input = kzalloc(count, GFP_KERNEL);
if (!input) {
kfree(input);
return -ENOMEM;
}
if (copy_from_user(input, buffer, count)) {
kfree(input);
return -EFAULT;
}
ret = kstrtouint(input, 0, &enable);
if (ret) {
kfree(input);
return -EINVAL;
}
if (enable != 0)
clk_prepare_enable(debug_clk);
else
clk_disable_unprepare(debug_clk);
return count;
}
static ssize_t rate_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
/*
* the rate unit is HZ, need 16 char
*/
char buf[16];
unsigned int len;
len = sprintf(buf, "%lu\n", clk_get_rate(debug_clk));
return simple_read_from_buffer(buffer, count, ppos, buf, len);
}
static ssize_t rate_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
int ret;
char *input;
unsigned long rate;
input = kzalloc(count, GFP_KERNEL);
if (!input) {
kfree(input);
return -ENOMEM;
}
if (copy_from_user(input, buffer, count)) {
kfree(input);
return -EFAULT;
}
ret = kstrtoul(input, 0, &rate);
if (ret) {
kfree(input);
return -EINVAL;
}
pr_info("input rate = %lu, old rate = %lu\n", rate,
clk_get_rate(debug_clk));
ret = clk_set_rate(debug_clk, rate);
if (ret) {
kfree(input);
pr_err("failed to set rate,ret =%d\n", ret);
return ret;
}
pr_info("current rate = %lu\n", clk_get_rate(debug_clk));
return count;
}
static ssize_t clk_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
char buf[48];
unsigned int len;
len = sprintf(buf, "get %s clock, its rate = %lu\n",
__clk_get_name(debug_clk),
clk_get_rate(debug_clk));
return simple_read_from_buffer(buffer, count, ppos, buf, len);
}
static ssize_t clk_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
int ret;
char clk_name[24];
char input[24];
struct clk *clk;
if (count >= sizeof(input))
return -EINVAL;
if (copy_from_user(input, buffer, count))
return -EFAULT;
input[count] = '\0';
ret = sscanf(input, "%s", clk_name);
if (ret != 1) {
pr_err("wrong usage, try: echo clk_name > clk\n");
return -EINVAL;
}
clk = __clk_lookup(clk_name);
if (clk)
pr_info("success get %s clock, its rate = %lu, its parent is %s\n",
clk_name, clk_get_rate(clk),
__clk_get_name(clk_get_parent(clk)));
else
pr_err("Can't find the clock, have a look in /sys/kernel/debug/clk.\n");
/* store the clk pointer */
debug_clk = clk;
return count;
}
static const struct file_operations parent_file_ops = {
.open = simple_open,
.write = parent_write,
};
static const struct file_operations enable_file_ops = {
.open = simple_open,
.read = enable_read,
.write = enable_write,
};
static const struct file_operations rate_file_ops = {
.open = simple_open,
.read = rate_read,
.write = rate_write,
};
static const struct file_operations clk_file_ops = {
.open = simple_open,
.read = clk_read,
.write = clk_write,
};
static int __init clk_debug_init(void)
{
struct dentry *root;
root = debugfs_create_dir("clk_debug", NULL);
debugfs_create_file("clk", 0600, root, NULL, &clk_file_ops);
debugfs_create_file("rate", 0600, root, NULL, &rate_file_ops);
debugfs_create_file("enable", 0600, root, NULL, &enable_file_ops);
debugfs_create_file("parent", 0200, root, NULL, &parent_file_ops);
return 0;
}
late_initcall_sync(clk_debug_init);
MODULE_DESCRIPTION("Amlogic meson clock debug driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jian hu <jian.hu@amlogic.com>");