13d0407baSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 23d0407baSopenharmony_ci/* 33d0407baSopenharmony_ci * Generic OPP debugfs interface 43d0407baSopenharmony_ci * 53d0407baSopenharmony_ci * Copyright (C) 2015-2016 Viresh Kumar <viresh.kumar@linaro.org> 63d0407baSopenharmony_ci */ 73d0407baSopenharmony_ci 83d0407baSopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 93d0407baSopenharmony_ci 103d0407baSopenharmony_ci#include <linux/debugfs.h> 113d0407baSopenharmony_ci#include <linux/device.h> 123d0407baSopenharmony_ci#include <linux/err.h> 133d0407baSopenharmony_ci#include <linux/init.h> 143d0407baSopenharmony_ci#include <linux/limits.h> 153d0407baSopenharmony_ci#include <linux/slab.h> 163d0407baSopenharmony_ci 173d0407baSopenharmony_ci#include "opp.h" 183d0407baSopenharmony_ci 193d0407baSopenharmony_cistatic struct dentry *rootdir; 203d0407baSopenharmony_ci 213d0407baSopenharmony_cistatic void opp_set_dev_name(const struct device *dev, char *name) 223d0407baSopenharmony_ci{ 233d0407baSopenharmony_ci if (dev->parent) { 243d0407baSopenharmony_ci snprintf(name, NAME_MAX, "%s-%s", dev_name(dev->parent), dev_name(dev)); 253d0407baSopenharmony_ci } else { 263d0407baSopenharmony_ci snprintf(name, NAME_MAX, "%s", dev_name(dev)); 273d0407baSopenharmony_ci } 283d0407baSopenharmony_ci} 293d0407baSopenharmony_ci 303d0407baSopenharmony_civoid opp_debug_remove_one(struct dev_pm_opp *opp) 313d0407baSopenharmony_ci{ 323d0407baSopenharmony_ci debugfs_remove_recursive(opp->dentry); 333d0407baSopenharmony_ci} 343d0407baSopenharmony_ci 353d0407baSopenharmony_cistatic ssize_t bw_name_read(struct file *fp, char __user *userbuf, size_t count, loff_t *ppos) 363d0407baSopenharmony_ci{ 373d0407baSopenharmony_ci struct icc_path *path = fp->private_data; 383d0407baSopenharmony_ci char buf[64]; 393d0407baSopenharmony_ci int i; 403d0407baSopenharmony_ci 413d0407baSopenharmony_ci i = scnprintf(buf, sizeof(buf), "%.62s\n", icc_get_name(path)); 423d0407baSopenharmony_ci 433d0407baSopenharmony_ci return simple_read_from_buffer(userbuf, count, ppos, buf, i); 443d0407baSopenharmony_ci} 453d0407baSopenharmony_ci 463d0407baSopenharmony_cistatic const struct file_operations bw_name_fops = { 473d0407baSopenharmony_ci .open = simple_open, 483d0407baSopenharmony_ci .read = bw_name_read, 493d0407baSopenharmony_ci .llseek = default_llseek, 503d0407baSopenharmony_ci}; 513d0407baSopenharmony_ci 523d0407baSopenharmony_cistatic void opp_debug_create_bw(struct dev_pm_opp *opp, struct opp_table *opp_table, struct dentry *pdentry) 533d0407baSopenharmony_ci{ 543d0407baSopenharmony_ci struct dentry *d; 553d0407baSopenharmony_ci char name[11]; 563d0407baSopenharmony_ci int i, ret; 573d0407baSopenharmony_ci 583d0407baSopenharmony_ci for (i = 0; i < opp_table->path_count; i++) { 593d0407baSopenharmony_ci ret = snprintf(name, sizeof(name), "icc-path-%.1d", i); 603d0407baSopenharmony_ci 613d0407baSopenharmony_ci /* Create per-path directory */ 623d0407baSopenharmony_ci d = debugfs_create_dir(name, pdentry); 633d0407baSopenharmony_ci 643d0407baSopenharmony_ci debugfs_create_file("name", S_IRUGO, d, opp_table->paths[i], &bw_name_fops); 653d0407baSopenharmony_ci debugfs_create_u32("peak_bw", S_IRUGO, d, &opp->bandwidth[i].peak); 663d0407baSopenharmony_ci debugfs_create_u32("avg_bw", S_IRUGO, d, &opp->bandwidth[i].avg); 673d0407baSopenharmony_ci } 683d0407baSopenharmony_ci} 693d0407baSopenharmony_ci 703d0407baSopenharmony_cistatic void opp_debug_create_supplies(struct dev_pm_opp *opp, struct opp_table *opp_table, struct dentry *pdentry) 713d0407baSopenharmony_ci{ 723d0407baSopenharmony_ci struct dentry *d; 733d0407baSopenharmony_ci int i, ret; 743d0407baSopenharmony_ci 753d0407baSopenharmony_ci for (i = 0; i < opp_table->regulator_count; i++) { 763d0407baSopenharmony_ci char name[15]; 773d0407baSopenharmony_ci 783d0407baSopenharmony_ci ret = snprintf(name, sizeof(name), "supply-%d", i); 793d0407baSopenharmony_ci 803d0407baSopenharmony_ci /* Create per-opp directory */ 813d0407baSopenharmony_ci d = debugfs_create_dir(name, pdentry); 823d0407baSopenharmony_ci 833d0407baSopenharmony_ci debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->supplies[i].u_volt); 843d0407baSopenharmony_ci 853d0407baSopenharmony_ci debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->supplies[i].u_volt_min); 863d0407baSopenharmony_ci 873d0407baSopenharmony_ci debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->supplies[i].u_volt_max); 883d0407baSopenharmony_ci 893d0407baSopenharmony_ci debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->supplies[i].u_amp); 903d0407baSopenharmony_ci } 913d0407baSopenharmony_ci} 923d0407baSopenharmony_ci 933d0407baSopenharmony_civoid opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) 943d0407baSopenharmony_ci{ 953d0407baSopenharmony_ci struct dentry *pdentry = opp_table->dentry; 963d0407baSopenharmony_ci struct dentry *d; 973d0407baSopenharmony_ci unsigned long id; 983d0407baSopenharmony_ci char name[25]; /* 20 chars for 64 bit value + 5 (opp:\0) */ 993d0407baSopenharmony_ci int ret; 1003d0407baSopenharmony_ci /* 1013d0407baSopenharmony_ci * Get directory name for OPP. 1023d0407baSopenharmony_ci * 1033d0407baSopenharmony_ci * - Normally rate is unique to each OPP, use it to get unique opp-name. 1043d0407baSopenharmony_ci * - For some devices rate isn't available, use index instead. 1053d0407baSopenharmony_ci */ 1063d0407baSopenharmony_ci if (likely(opp->rate)) { 1073d0407baSopenharmony_ci id = opp->rate; 1083d0407baSopenharmony_ci } else { 1093d0407baSopenharmony_ci id = _get_opp_count(opp_table); 1103d0407baSopenharmony_ci } 1113d0407baSopenharmony_ci 1123d0407baSopenharmony_ci ret = snprintf(name, sizeof(name), "opp:%lu", id); 1133d0407baSopenharmony_ci 1143d0407baSopenharmony_ci /* Create per-opp directory */ 1153d0407baSopenharmony_ci d = debugfs_create_dir(name, pdentry); 1163d0407baSopenharmony_ci 1173d0407baSopenharmony_ci debugfs_create_bool("available", S_IRUGO, d, &opp->available); 1183d0407baSopenharmony_ci debugfs_create_bool("dynamic", S_IRUGO, d, &opp->dynamic); 1193d0407baSopenharmony_ci debugfs_create_bool("turbo", S_IRUGO, d, &opp->turbo); 1203d0407baSopenharmony_ci debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend); 1213d0407baSopenharmony_ci debugfs_create_u32("performance_state", S_IRUGO, d, &opp->pstate); 1223d0407baSopenharmony_ci debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate); 1233d0407baSopenharmony_ci debugfs_create_ulong("clock_latency_ns", S_IRUGO, d, &opp->clock_latency_ns); 1243d0407baSopenharmony_ci 1253d0407baSopenharmony_ci opp_debug_create_supplies(opp, opp_table, d); 1263d0407baSopenharmony_ci opp_debug_create_bw(opp, opp_table, d); 1273d0407baSopenharmony_ci 1283d0407baSopenharmony_ci opp->dentry = d; 1293d0407baSopenharmony_ci} 1303d0407baSopenharmony_ci 1313d0407baSopenharmony_cistatic void opp_list_debug_create_dir(struct opp_device *opp_dev, struct opp_table *opp_table) 1323d0407baSopenharmony_ci{ 1333d0407baSopenharmony_ci const struct device *dev = opp_dev->dev; 1343d0407baSopenharmony_ci struct dentry *d; 1353d0407baSopenharmony_ci 1363d0407baSopenharmony_ci opp_set_dev_name(dev, opp_table->dentry_name); 1373d0407baSopenharmony_ci 1383d0407baSopenharmony_ci /* Create device specific directory */ 1393d0407baSopenharmony_ci d = debugfs_create_dir(opp_table->dentry_name, rootdir); 1403d0407baSopenharmony_ci 1413d0407baSopenharmony_ci opp_dev->dentry = d; 1423d0407baSopenharmony_ci opp_table->dentry = d; 1433d0407baSopenharmony_ci} 1443d0407baSopenharmony_ci 1453d0407baSopenharmony_cistatic void opp_list_debug_create_link(struct opp_device *opp_dev, struct opp_table *opp_table) 1463d0407baSopenharmony_ci{ 1473d0407baSopenharmony_ci char name[NAME_MAX]; 1483d0407baSopenharmony_ci 1493d0407baSopenharmony_ci opp_set_dev_name(opp_dev->dev, name); 1503d0407baSopenharmony_ci 1513d0407baSopenharmony_ci /* Create device specific directory link */ 1523d0407baSopenharmony_ci opp_dev->dentry = debugfs_create_symlink(name, rootdir, opp_table->dentry_name); 1533d0407baSopenharmony_ci} 1543d0407baSopenharmony_ci 1553d0407baSopenharmony_ci/** 1563d0407baSopenharmony_ci * opp_debug_register - add a device opp node to the debugfs 'opp' directory 1573d0407baSopenharmony_ci * @opp_dev: opp-dev pointer for device 1583d0407baSopenharmony_ci * @opp_table: the device-opp being added 1593d0407baSopenharmony_ci * 1603d0407baSopenharmony_ci * Dynamically adds device specific directory in debugfs 'opp' directory. If the 1613d0407baSopenharmony_ci * device-opp is shared with other devices, then links will be created for all 1623d0407baSopenharmony_ci * devices except the first. 1633d0407baSopenharmony_ci */ 1643d0407baSopenharmony_civoid opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table) 1653d0407baSopenharmony_ci{ 1663d0407baSopenharmony_ci if (opp_table->dentry) { 1673d0407baSopenharmony_ci opp_list_debug_create_link(opp_dev, opp_table); 1683d0407baSopenharmony_ci } else { 1693d0407baSopenharmony_ci opp_list_debug_create_dir(opp_dev, opp_table); 1703d0407baSopenharmony_ci } 1713d0407baSopenharmony_ci} 1723d0407baSopenharmony_ci 1733d0407baSopenharmony_cistatic void opp_migrate_dentry(struct opp_device *opp_dev, struct opp_table *opp_table) 1743d0407baSopenharmony_ci{ 1753d0407baSopenharmony_ci struct opp_device *new_dev; 1763d0407baSopenharmony_ci const struct device *dev; 1773d0407baSopenharmony_ci struct dentry *dentry; 1783d0407baSopenharmony_ci 1793d0407baSopenharmony_ci /* Look for next opp-dev */ 1803d0407baSopenharmony_ci list_for_each_entry(new_dev, &opp_table->dev_list, node) if (new_dev != opp_dev) break; 1813d0407baSopenharmony_ci 1823d0407baSopenharmony_ci /* new_dev is guaranteed to be valid here */ 1833d0407baSopenharmony_ci dev = new_dev->dev; 1843d0407baSopenharmony_ci debugfs_remove_recursive(new_dev->dentry); 1853d0407baSopenharmony_ci 1863d0407baSopenharmony_ci opp_set_dev_name(dev, opp_table->dentry_name); 1873d0407baSopenharmony_ci 1883d0407baSopenharmony_ci dentry = debugfs_rename(rootdir, opp_dev->dentry, rootdir, opp_table->dentry_name); 1893d0407baSopenharmony_ci if (!dentry) { 1903d0407baSopenharmony_ci dev_err(dev, "%s: Failed to rename link from: %s to %s\n", __func__, dev_name(opp_dev->dev), dev_name(dev)); 1913d0407baSopenharmony_ci return; 1923d0407baSopenharmony_ci } 1933d0407baSopenharmony_ci 1943d0407baSopenharmony_ci new_dev->dentry = dentry; 1953d0407baSopenharmony_ci opp_table->dentry = dentry; 1963d0407baSopenharmony_ci} 1973d0407baSopenharmony_ci 1983d0407baSopenharmony_ci/** 1993d0407baSopenharmony_ci * opp_debug_unregister - remove a device opp node from debugfs opp directory 2003d0407baSopenharmony_ci * @opp_dev: opp-dev pointer for device 2013d0407baSopenharmony_ci * @opp_table: the device-opp being removed 2023d0407baSopenharmony_ci * 2033d0407baSopenharmony_ci * Dynamically removes device specific directory from debugfs 'opp' directory. 2043d0407baSopenharmony_ci */ 2053d0407baSopenharmony_civoid opp_debug_unregister(struct opp_device *opp_dev, struct opp_table *opp_table) 2063d0407baSopenharmony_ci{ 2073d0407baSopenharmony_ci if (opp_dev->dentry == opp_table->dentry) { 2083d0407baSopenharmony_ci /* Move the real dentry object under another device */ 2093d0407baSopenharmony_ci if (!list_is_singular(&opp_table->dev_list)) { 2103d0407baSopenharmony_ci opp_migrate_dentry(opp_dev, opp_table); 2113d0407baSopenharmony_ci goto out; 2123d0407baSopenharmony_ci } 2133d0407baSopenharmony_ci opp_table->dentry = NULL; 2143d0407baSopenharmony_ci } 2153d0407baSopenharmony_ci 2163d0407baSopenharmony_ci debugfs_remove_recursive(opp_dev->dentry); 2173d0407baSopenharmony_ci 2183d0407baSopenharmony_ciout: 2193d0407baSopenharmony_ci opp_dev->dentry = NULL; 2203d0407baSopenharmony_ci} 2213d0407baSopenharmony_ci 2223d0407baSopenharmony_cistatic int opp_summary_show(struct seq_file *s, void *data) 2233d0407baSopenharmony_ci{ 2243d0407baSopenharmony_ci struct list_head *lists = (struct list_head *)s->private; 2253d0407baSopenharmony_ci struct opp_table *opp_table; 2263d0407baSopenharmony_ci struct dev_pm_opp *opp; 2273d0407baSopenharmony_ci 2283d0407baSopenharmony_ci mutex_lock(&opp_table_lock); 2293d0407baSopenharmony_ci 2303d0407baSopenharmony_ci seq_puts(s, " device rate(Hz) target(uV) min(uV) max(uV)\n"); 2313d0407baSopenharmony_ci seq_puts(s, "-------------------------------------------------------------------\n"); 2323d0407baSopenharmony_ci 2333d0407baSopenharmony_ci list_for_each_entry(opp_table, lists, node) 2343d0407baSopenharmony_ci { 2353d0407baSopenharmony_ci seq_printf(s, " %s\n", opp_table->dentry_name); 2363d0407baSopenharmony_ci mutex_lock(&opp_table->lock); 2373d0407baSopenharmony_ci list_for_each_entry(opp, &opp_table->opp_list, node) 2383d0407baSopenharmony_ci { 2393d0407baSopenharmony_ci seq_printf(s, "%31lu %12lu %11lu %11lu\n", opp->rate, opp->supplies[0].u_volt, opp->supplies[0].u_volt_min, 2403d0407baSopenharmony_ci opp->supplies[0].u_volt_max); 2413d0407baSopenharmony_ci } 2423d0407baSopenharmony_ci mutex_unlock(&opp_table->lock); 2433d0407baSopenharmony_ci } 2443d0407baSopenharmony_ci 2453d0407baSopenharmony_ci mutex_unlock(&opp_table_lock); 2463d0407baSopenharmony_ci 2473d0407baSopenharmony_ci return 0; 2483d0407baSopenharmony_ci} 2493d0407baSopenharmony_ci 2503d0407baSopenharmony_cistatic int opp_summary_open(struct inode *inode, struct file *file) 2513d0407baSopenharmony_ci{ 2523d0407baSopenharmony_ci return single_open(file, opp_summary_show, inode->i_private); 2533d0407baSopenharmony_ci} 2543d0407baSopenharmony_ci 2553d0407baSopenharmony_cistatic const struct file_operations opp_summary_fops = { 2563d0407baSopenharmony_ci .open = opp_summary_open, 2573d0407baSopenharmony_ci .read = seq_read, 2583d0407baSopenharmony_ci .llseek = seq_lseek, 2593d0407baSopenharmony_ci .release = single_release, 2603d0407baSopenharmony_ci}; 2613d0407baSopenharmony_ci 2623d0407baSopenharmony_cistatic int __init opp_debug_init(void) 2633d0407baSopenharmony_ci{ 2643d0407baSopenharmony_ci /* Create /sys/kernel/debug/opp directory */ 2653d0407baSopenharmony_ci rootdir = debugfs_create_dir("opp", NULL); 2663d0407baSopenharmony_ci 2673d0407baSopenharmony_ci debugfs_create_file("opp_summary", 0444, rootdir, &opp_tables, &opp_summary_fops); 2683d0407baSopenharmony_ci 2693d0407baSopenharmony_ci return 0; 2703d0407baSopenharmony_ci} 2713d0407baSopenharmony_cicore_initcall(opp_debug_init); 272