// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include 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[20]; char input[20]; struct clk *clk; if (count >= sizeof(input)) { pr_err("wrong notice: clk_name too long > 20, pls shorten clk_name\n"); 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_get_sys(clk_name, NULL); if (IS_ERR_OR_NULL(clk)) { /* error code or NULL may be returned */ pr_err("Can't find the clock, have a look in /sys/kernel/debug/clk\n"); return -EPERM; } 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 measure_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { int ret; char input[5]; unsigned int id = 0, rate; if (count == 1) return -EFAULT; count = min_t(size_t, count, (sizeof(input) - 1)); if (copy_from_user(input, buffer, count)) return -EFAULT; input[count] = '\0'; ret = kstrtouint(input, 0, &id); if (ret) return -EINVAL; rate = meson_clk_measure(id); pr_info("Measure the index %u clock,its rate = %u\n", id, rate); 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 = 0; 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); kfree(input); 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 = 0; 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)); kfree(input); 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[20]; char input[20]; struct clk *clk; if (count >= sizeof(input)) { pr_err("wrong notice: clk_name too long > 20, pls shorten clk_name\n"); 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_get_sys(clk_name, NULL); /* error code or NULL may be returned */ if (!IS_ERR_OR_NULL(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))); /* store the clk pointer */ if (debug_clk) clk_put(debug_clk); debug_clk = clk; } else { pr_err("Can't find the clock, have a look in /sys/kernel/debug/clk.\n"); } return count; } static ssize_t secure_reg_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { int ret; char *input; unsigned long reg = 0; struct arm_smccc_res res; 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, ®); if (ret) { kfree(input); return -EINVAL; } arm_smccc_smc(0x82000099, 0x30, reg, 0, 0, 0, 0, 0, &res); kfree(input); return count; } static const struct file_operations parent_file_ops = { .open = simple_open, .write = parent_write, }; static const struct file_operations measure_file_ops = { .open = simple_open, .write = measure_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 const struct file_operations secure_file_ops = { .open = simple_open, .write = secure_reg_write, }; 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); debugfs_create_file("measure", 0200, root, NULL, &measure_file_ops); debugfs_create_file("secure_reg", 0200, root, NULL, &secure_file_ops); return 0; } #ifndef MODULE late_initcall_sync(clk_debug_init); #endif MODULE_DESCRIPTION("Amlogic meson clock debug driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("jian hu ");