162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Generic OPP debugfs interface 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2015-2016 Viresh Kumar <viresh.kumar@linaro.org> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/debugfs.h> 1162306a36Sopenharmony_ci#include <linux/device.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/init.h> 1562306a36Sopenharmony_ci#include <linux/limits.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include "opp.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic struct dentry *rootdir; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic void opp_set_dev_name(const struct device *dev, char *name) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci if (dev->parent) 2562306a36Sopenharmony_ci snprintf(name, NAME_MAX, "%s-%s", dev_name(dev->parent), 2662306a36Sopenharmony_ci dev_name(dev)); 2762306a36Sopenharmony_ci else 2862306a36Sopenharmony_ci snprintf(name, NAME_MAX, "%s", dev_name(dev)); 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_civoid opp_debug_remove_one(struct dev_pm_opp *opp) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci debugfs_remove_recursive(opp->dentry); 3462306a36Sopenharmony_ci} 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic ssize_t bw_name_read(struct file *fp, char __user *userbuf, 3762306a36Sopenharmony_ci size_t count, loff_t *ppos) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci struct icc_path *path = fp->private_data; 4062306a36Sopenharmony_ci const char *name = icc_get_name(path); 4162306a36Sopenharmony_ci char buf[64]; 4262306a36Sopenharmony_ci int i = 0; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci if (name) 4562306a36Sopenharmony_ci i = scnprintf(buf, sizeof(buf), "%.62s\n", name); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci return simple_read_from_buffer(userbuf, count, ppos, buf, i); 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic const struct file_operations bw_name_fops = { 5162306a36Sopenharmony_ci .open = simple_open, 5262306a36Sopenharmony_ci .read = bw_name_read, 5362306a36Sopenharmony_ci .llseek = default_llseek, 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic void opp_debug_create_bw(struct dev_pm_opp *opp, 5762306a36Sopenharmony_ci struct opp_table *opp_table, 5862306a36Sopenharmony_ci struct dentry *pdentry) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci struct dentry *d; 6162306a36Sopenharmony_ci char name[11]; 6262306a36Sopenharmony_ci int i; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci for (i = 0; i < opp_table->path_count; i++) { 6562306a36Sopenharmony_ci snprintf(name, sizeof(name), "icc-path-%.1d", i); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci /* Create per-path directory */ 6862306a36Sopenharmony_ci d = debugfs_create_dir(name, pdentry); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci debugfs_create_file("name", S_IRUGO, d, opp_table->paths[i], 7162306a36Sopenharmony_ci &bw_name_fops); 7262306a36Sopenharmony_ci debugfs_create_u32("peak_bw", S_IRUGO, d, 7362306a36Sopenharmony_ci &opp->bandwidth[i].peak); 7462306a36Sopenharmony_ci debugfs_create_u32("avg_bw", S_IRUGO, d, 7562306a36Sopenharmony_ci &opp->bandwidth[i].avg); 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic void opp_debug_create_clks(struct dev_pm_opp *opp, 8062306a36Sopenharmony_ci struct opp_table *opp_table, 8162306a36Sopenharmony_ci struct dentry *pdentry) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci char name[12]; 8462306a36Sopenharmony_ci int i; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (opp_table->clk_count == 1) { 8762306a36Sopenharmony_ci debugfs_create_ulong("rate_hz", S_IRUGO, pdentry, &opp->rates[0]); 8862306a36Sopenharmony_ci return; 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci for (i = 0; i < opp_table->clk_count; i++) { 9262306a36Sopenharmony_ci snprintf(name, sizeof(name), "rate_hz_%d", i); 9362306a36Sopenharmony_ci debugfs_create_ulong(name, S_IRUGO, pdentry, &opp->rates[i]); 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic void opp_debug_create_supplies(struct dev_pm_opp *opp, 9862306a36Sopenharmony_ci struct opp_table *opp_table, 9962306a36Sopenharmony_ci struct dentry *pdentry) 10062306a36Sopenharmony_ci{ 10162306a36Sopenharmony_ci struct dentry *d; 10262306a36Sopenharmony_ci int i; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci for (i = 0; i < opp_table->regulator_count; i++) { 10562306a36Sopenharmony_ci char name[15]; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci snprintf(name, sizeof(name), "supply-%d", i); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci /* Create per-opp directory */ 11062306a36Sopenharmony_ci d = debugfs_create_dir(name, pdentry); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci debugfs_create_ulong("u_volt_target", S_IRUGO, d, 11362306a36Sopenharmony_ci &opp->supplies[i].u_volt); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci debugfs_create_ulong("u_volt_min", S_IRUGO, d, 11662306a36Sopenharmony_ci &opp->supplies[i].u_volt_min); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci debugfs_create_ulong("u_volt_max", S_IRUGO, d, 11962306a36Sopenharmony_ci &opp->supplies[i].u_volt_max); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci debugfs_create_ulong("u_amp", S_IRUGO, d, 12262306a36Sopenharmony_ci &opp->supplies[i].u_amp); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci debugfs_create_ulong("u_watt", S_IRUGO, d, 12562306a36Sopenharmony_ci &opp->supplies[i].u_watt); 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_civoid opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct dentry *pdentry = opp_table->dentry; 13262306a36Sopenharmony_ci struct dentry *d; 13362306a36Sopenharmony_ci unsigned long id; 13462306a36Sopenharmony_ci char name[25]; /* 20 chars for 64 bit value + 5 (opp:\0) */ 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci /* 13762306a36Sopenharmony_ci * Get directory name for OPP. 13862306a36Sopenharmony_ci * 13962306a36Sopenharmony_ci * - Normally rate is unique to each OPP, use it to get unique opp-name. 14062306a36Sopenharmony_ci * - For some devices rate isn't available or there are multiple, use 14162306a36Sopenharmony_ci * index instead for them. 14262306a36Sopenharmony_ci */ 14362306a36Sopenharmony_ci if (likely(opp_table->clk_count == 1 && opp->rates[0])) 14462306a36Sopenharmony_ci id = opp->rates[0]; 14562306a36Sopenharmony_ci else 14662306a36Sopenharmony_ci id = _get_opp_count(opp_table); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci snprintf(name, sizeof(name), "opp:%lu", id); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci /* Create per-opp directory */ 15162306a36Sopenharmony_ci d = debugfs_create_dir(name, pdentry); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci debugfs_create_bool("available", S_IRUGO, d, &opp->available); 15462306a36Sopenharmony_ci debugfs_create_bool("dynamic", S_IRUGO, d, &opp->dynamic); 15562306a36Sopenharmony_ci debugfs_create_bool("turbo", S_IRUGO, d, &opp->turbo); 15662306a36Sopenharmony_ci debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend); 15762306a36Sopenharmony_ci debugfs_create_u32("level", S_IRUGO, d, &opp->level); 15862306a36Sopenharmony_ci debugfs_create_ulong("clock_latency_ns", S_IRUGO, d, 15962306a36Sopenharmony_ci &opp->clock_latency_ns); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci opp->of_name = of_node_full_name(opp->np); 16262306a36Sopenharmony_ci debugfs_create_str("of_name", S_IRUGO, d, (char **)&opp->of_name); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci opp_debug_create_clks(opp, opp_table, d); 16562306a36Sopenharmony_ci opp_debug_create_supplies(opp, opp_table, d); 16662306a36Sopenharmony_ci opp_debug_create_bw(opp, opp_table, d); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci opp->dentry = d; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_cistatic void opp_list_debug_create_dir(struct opp_device *opp_dev, 17262306a36Sopenharmony_ci struct opp_table *opp_table) 17362306a36Sopenharmony_ci{ 17462306a36Sopenharmony_ci const struct device *dev = opp_dev->dev; 17562306a36Sopenharmony_ci struct dentry *d; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci opp_set_dev_name(dev, opp_table->dentry_name); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* Create device specific directory */ 18062306a36Sopenharmony_ci d = debugfs_create_dir(opp_table->dentry_name, rootdir); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci opp_dev->dentry = d; 18362306a36Sopenharmony_ci opp_table->dentry = d; 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_cistatic void opp_list_debug_create_link(struct opp_device *opp_dev, 18762306a36Sopenharmony_ci struct opp_table *opp_table) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci char name[NAME_MAX]; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci opp_set_dev_name(opp_dev->dev, name); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci /* Create device specific directory link */ 19462306a36Sopenharmony_ci opp_dev->dentry = debugfs_create_symlink(name, rootdir, 19562306a36Sopenharmony_ci opp_table->dentry_name); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci/** 19962306a36Sopenharmony_ci * opp_debug_register - add a device opp node to the debugfs 'opp' directory 20062306a36Sopenharmony_ci * @opp_dev: opp-dev pointer for device 20162306a36Sopenharmony_ci * @opp_table: the device-opp being added 20262306a36Sopenharmony_ci * 20362306a36Sopenharmony_ci * Dynamically adds device specific directory in debugfs 'opp' directory. If the 20462306a36Sopenharmony_ci * device-opp is shared with other devices, then links will be created for all 20562306a36Sopenharmony_ci * devices except the first. 20662306a36Sopenharmony_ci */ 20762306a36Sopenharmony_civoid opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci if (opp_table->dentry) 21062306a36Sopenharmony_ci opp_list_debug_create_link(opp_dev, opp_table); 21162306a36Sopenharmony_ci else 21262306a36Sopenharmony_ci opp_list_debug_create_dir(opp_dev, opp_table); 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic void opp_migrate_dentry(struct opp_device *opp_dev, 21662306a36Sopenharmony_ci struct opp_table *opp_table) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci struct opp_device *new_dev = NULL, *iter; 21962306a36Sopenharmony_ci const struct device *dev; 22062306a36Sopenharmony_ci struct dentry *dentry; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci /* Look for next opp-dev */ 22362306a36Sopenharmony_ci list_for_each_entry(iter, &opp_table->dev_list, node) 22462306a36Sopenharmony_ci if (iter != opp_dev) { 22562306a36Sopenharmony_ci new_dev = iter; 22662306a36Sopenharmony_ci break; 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci BUG_ON(!new_dev); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* new_dev is guaranteed to be valid here */ 23262306a36Sopenharmony_ci dev = new_dev->dev; 23362306a36Sopenharmony_ci debugfs_remove_recursive(new_dev->dentry); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci opp_set_dev_name(dev, opp_table->dentry_name); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci dentry = debugfs_rename(rootdir, opp_dev->dentry, rootdir, 23862306a36Sopenharmony_ci opp_table->dentry_name); 23962306a36Sopenharmony_ci if (IS_ERR(dentry)) { 24062306a36Sopenharmony_ci dev_err(dev, "%s: Failed to rename link from: %s to %s\n", 24162306a36Sopenharmony_ci __func__, dev_name(opp_dev->dev), dev_name(dev)); 24262306a36Sopenharmony_ci return; 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci new_dev->dentry = dentry; 24662306a36Sopenharmony_ci opp_table->dentry = dentry; 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci/** 25062306a36Sopenharmony_ci * opp_debug_unregister - remove a device opp node from debugfs opp directory 25162306a36Sopenharmony_ci * @opp_dev: opp-dev pointer for device 25262306a36Sopenharmony_ci * @opp_table: the device-opp being removed 25362306a36Sopenharmony_ci * 25462306a36Sopenharmony_ci * Dynamically removes device specific directory from debugfs 'opp' directory. 25562306a36Sopenharmony_ci */ 25662306a36Sopenharmony_civoid opp_debug_unregister(struct opp_device *opp_dev, 25762306a36Sopenharmony_ci struct opp_table *opp_table) 25862306a36Sopenharmony_ci{ 25962306a36Sopenharmony_ci if (opp_dev->dentry == opp_table->dentry) { 26062306a36Sopenharmony_ci /* Move the real dentry object under another device */ 26162306a36Sopenharmony_ci if (!list_is_singular(&opp_table->dev_list)) { 26262306a36Sopenharmony_ci opp_migrate_dentry(opp_dev, opp_table); 26362306a36Sopenharmony_ci goto out; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci opp_table->dentry = NULL; 26662306a36Sopenharmony_ci } 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci debugfs_remove_recursive(opp_dev->dentry); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ciout: 27162306a36Sopenharmony_ci opp_dev->dentry = NULL; 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_cistatic int __init opp_debug_init(void) 27562306a36Sopenharmony_ci{ 27662306a36Sopenharmony_ci /* Create /sys/kernel/debug/opp directory */ 27762306a36Sopenharmony_ci rootdir = debugfs_create_dir("opp", NULL); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_cicore_initcall(opp_debug_init); 282