1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 27813dd6fSViresh Kumar /* 37813dd6fSViresh Kumar * Generic OPP debugfs interface 47813dd6fSViresh Kumar * 57813dd6fSViresh Kumar * Copyright (C) 2015-2016 Viresh Kumar <viresh.kumar@linaro.org> 67813dd6fSViresh Kumar */ 77813dd6fSViresh Kumar 87813dd6fSViresh Kumar #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 97813dd6fSViresh Kumar 107813dd6fSViresh Kumar #include <linux/debugfs.h> 117813dd6fSViresh Kumar #include <linux/device.h> 127813dd6fSViresh Kumar #include <linux/err.h> 13021dbecaSViresh Kumar #include <linux/of.h> 147813dd6fSViresh Kumar #include <linux/init.h> 157813dd6fSViresh Kumar #include <linux/limits.h> 167813dd6fSViresh Kumar #include <linux/slab.h> 177813dd6fSViresh Kumar 187813dd6fSViresh Kumar #include "opp.h" 197813dd6fSViresh Kumar 207813dd6fSViresh Kumar static struct dentry *rootdir; 217813dd6fSViresh Kumar 227813dd6fSViresh Kumar static void opp_set_dev_name(const struct device *dev, char *name) 237813dd6fSViresh Kumar { 247813dd6fSViresh Kumar if (dev->parent) 257813dd6fSViresh Kumar snprintf(name, NAME_MAX, "%s-%s", dev_name(dev->parent), 267813dd6fSViresh Kumar dev_name(dev)); 277813dd6fSViresh Kumar else 287813dd6fSViresh Kumar snprintf(name, NAME_MAX, "%s", dev_name(dev)); 297813dd6fSViresh Kumar } 307813dd6fSViresh Kumar 317813dd6fSViresh Kumar void opp_debug_remove_one(struct dev_pm_opp *opp) 327813dd6fSViresh Kumar { 337813dd6fSViresh Kumar debugfs_remove_recursive(opp->dentry); 347813dd6fSViresh Kumar } 357813dd6fSViresh Kumar 360430b1d5SViresh Kumar static ssize_t bw_name_read(struct file *fp, char __user *userbuf, 370430b1d5SViresh Kumar size_t count, loff_t *ppos) 380430b1d5SViresh Kumar { 390430b1d5SViresh Kumar struct icc_path *path = fp->private_data; 400430b1d5SViresh Kumar char buf[64]; 410430b1d5SViresh Kumar int i; 420430b1d5SViresh Kumar 430430b1d5SViresh Kumar i = scnprintf(buf, sizeof(buf), "%.62s\n", icc_get_name(path)); 440430b1d5SViresh Kumar 450430b1d5SViresh Kumar return simple_read_from_buffer(userbuf, count, ppos, buf, i); 460430b1d5SViresh Kumar } 470430b1d5SViresh Kumar 480430b1d5SViresh Kumar static const struct file_operations bw_name_fops = { 490430b1d5SViresh Kumar .open = simple_open, 500430b1d5SViresh Kumar .read = bw_name_read, 510430b1d5SViresh Kumar .llseek = default_llseek, 520430b1d5SViresh Kumar }; 530430b1d5SViresh Kumar 540430b1d5SViresh Kumar static void opp_debug_create_bw(struct dev_pm_opp *opp, 550430b1d5SViresh Kumar struct opp_table *opp_table, 560430b1d5SViresh Kumar struct dentry *pdentry) 570430b1d5SViresh Kumar { 580430b1d5SViresh Kumar struct dentry *d; 590430b1d5SViresh Kumar char name[11]; 600430b1d5SViresh Kumar int i; 610430b1d5SViresh Kumar 620430b1d5SViresh Kumar for (i = 0; i < opp_table->path_count; i++) { 630430b1d5SViresh Kumar snprintf(name, sizeof(name), "icc-path-%.1d", i); 640430b1d5SViresh Kumar 650430b1d5SViresh Kumar /* Create per-path directory */ 660430b1d5SViresh Kumar d = debugfs_create_dir(name, pdentry); 670430b1d5SViresh Kumar 680430b1d5SViresh Kumar debugfs_create_file("name", S_IRUGO, d, opp_table->paths[i], 690430b1d5SViresh Kumar &bw_name_fops); 700430b1d5SViresh Kumar debugfs_create_u32("peak_bw", S_IRUGO, d, 710430b1d5SViresh Kumar &opp->bandwidth[i].peak); 720430b1d5SViresh Kumar debugfs_create_u32("avg_bw", S_IRUGO, d, 730430b1d5SViresh Kumar &opp->bandwidth[i].avg); 740430b1d5SViresh Kumar } 750430b1d5SViresh Kumar } 760430b1d5SViresh Kumar 77a2dea4cbSGreg Kroah-Hartman static void opp_debug_create_supplies(struct dev_pm_opp *opp, 787813dd6fSViresh Kumar struct opp_table *opp_table, 797813dd6fSViresh Kumar struct dentry *pdentry) 807813dd6fSViresh Kumar { 817813dd6fSViresh Kumar struct dentry *d; 827813dd6fSViresh Kumar int i; 837813dd6fSViresh Kumar 847813dd6fSViresh Kumar for (i = 0; i < opp_table->regulator_count; i++) { 85d741029aSArvind Yadav char name[15]; 86d741029aSArvind Yadav 87d741029aSArvind Yadav snprintf(name, sizeof(name), "supply-%d", i); 887813dd6fSViresh Kumar 897813dd6fSViresh Kumar /* Create per-opp directory */ 907813dd6fSViresh Kumar d = debugfs_create_dir(name, pdentry); 917813dd6fSViresh Kumar 92a2dea4cbSGreg Kroah-Hartman debugfs_create_ulong("u_volt_target", S_IRUGO, d, 93a2dea4cbSGreg Kroah-Hartman &opp->supplies[i].u_volt); 947813dd6fSViresh Kumar 95a2dea4cbSGreg Kroah-Hartman debugfs_create_ulong("u_volt_min", S_IRUGO, d, 96a2dea4cbSGreg Kroah-Hartman &opp->supplies[i].u_volt_min); 977813dd6fSViresh Kumar 98a2dea4cbSGreg Kroah-Hartman debugfs_create_ulong("u_volt_max", S_IRUGO, d, 99a2dea4cbSGreg Kroah-Hartman &opp->supplies[i].u_volt_max); 1007813dd6fSViresh Kumar 101a2dea4cbSGreg Kroah-Hartman debugfs_create_ulong("u_amp", S_IRUGO, d, 102a2dea4cbSGreg Kroah-Hartman &opp->supplies[i].u_amp); 103*4f9a7a1dSLukasz Luba 104*4f9a7a1dSLukasz Luba debugfs_create_ulong("u_watt", S_IRUGO, d, 105*4f9a7a1dSLukasz Luba &opp->supplies[i].u_watt); 106a2dea4cbSGreg Kroah-Hartman } 1077813dd6fSViresh Kumar } 1087813dd6fSViresh Kumar 109a2dea4cbSGreg Kroah-Hartman void opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) 1107813dd6fSViresh Kumar { 1117813dd6fSViresh Kumar struct dentry *pdentry = opp_table->dentry; 1127813dd6fSViresh Kumar struct dentry *d; 113a1e8c136SViresh Kumar unsigned long id; 1147813dd6fSViresh Kumar char name[25]; /* 20 chars for 64 bit value + 5 (opp:\0) */ 1157813dd6fSViresh Kumar 116a1e8c136SViresh Kumar /* 117a1e8c136SViresh Kumar * Get directory name for OPP. 118a1e8c136SViresh Kumar * 119a1e8c136SViresh Kumar * - Normally rate is unique to each OPP, use it to get unique opp-name. 120a1e8c136SViresh Kumar * - For some devices rate isn't available, use index instead. 121a1e8c136SViresh Kumar */ 122a1e8c136SViresh Kumar if (likely(opp->rate)) 123a1e8c136SViresh Kumar id = opp->rate; 124a1e8c136SViresh Kumar else 125a1e8c136SViresh Kumar id = _get_opp_count(opp_table); 126a1e8c136SViresh Kumar 127a1e8c136SViresh Kumar snprintf(name, sizeof(name), "opp:%lu", id); 1287813dd6fSViresh Kumar 1297813dd6fSViresh Kumar /* Create per-opp directory */ 1307813dd6fSViresh Kumar d = debugfs_create_dir(name, pdentry); 1317813dd6fSViresh Kumar 132a2dea4cbSGreg Kroah-Hartman debugfs_create_bool("available", S_IRUGO, d, &opp->available); 133a2dea4cbSGreg Kroah-Hartman debugfs_create_bool("dynamic", S_IRUGO, d, &opp->dynamic); 134a2dea4cbSGreg Kroah-Hartman debugfs_create_bool("turbo", S_IRUGO, d, &opp->turbo); 135a2dea4cbSGreg Kroah-Hartman debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend); 136a2dea4cbSGreg Kroah-Hartman debugfs_create_u32("performance_state", S_IRUGO, d, &opp->pstate); 137a2dea4cbSGreg Kroah-Hartman debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate); 138021dbecaSViresh Kumar debugfs_create_u32("level", S_IRUGO, d, &opp->level); 139a2dea4cbSGreg Kroah-Hartman debugfs_create_ulong("clock_latency_ns", S_IRUGO, d, 140a2dea4cbSGreg Kroah-Hartman &opp->clock_latency_ns); 1417813dd6fSViresh Kumar 142021dbecaSViresh Kumar opp->of_name = of_node_full_name(opp->np); 143021dbecaSViresh Kumar debugfs_create_str("of_name", S_IRUGO, d, (char **)&opp->of_name); 144021dbecaSViresh Kumar 145a2dea4cbSGreg Kroah-Hartman opp_debug_create_supplies(opp, opp_table, d); 1460430b1d5SViresh Kumar opp_debug_create_bw(opp, opp_table, d); 1477813dd6fSViresh Kumar 1487813dd6fSViresh Kumar opp->dentry = d; 1497813dd6fSViresh Kumar } 1507813dd6fSViresh Kumar 151a2dea4cbSGreg Kroah-Hartman static void opp_list_debug_create_dir(struct opp_device *opp_dev, 1527813dd6fSViresh Kumar struct opp_table *opp_table) 1537813dd6fSViresh Kumar { 1547813dd6fSViresh Kumar const struct device *dev = opp_dev->dev; 1557813dd6fSViresh Kumar struct dentry *d; 1567813dd6fSViresh Kumar 1577813dd6fSViresh Kumar opp_set_dev_name(dev, opp_table->dentry_name); 1587813dd6fSViresh Kumar 1597813dd6fSViresh Kumar /* Create device specific directory */ 1607813dd6fSViresh Kumar d = debugfs_create_dir(opp_table->dentry_name, rootdir); 1617813dd6fSViresh Kumar 1627813dd6fSViresh Kumar opp_dev->dentry = d; 1637813dd6fSViresh Kumar opp_table->dentry = d; 1647813dd6fSViresh Kumar } 1657813dd6fSViresh Kumar 166a2dea4cbSGreg Kroah-Hartman static void opp_list_debug_create_link(struct opp_device *opp_dev, 1677813dd6fSViresh Kumar struct opp_table *opp_table) 1687813dd6fSViresh Kumar { 1697813dd6fSViresh Kumar char name[NAME_MAX]; 1707813dd6fSViresh Kumar 1717813dd6fSViresh Kumar opp_set_dev_name(opp_dev->dev, name); 1727813dd6fSViresh Kumar 1737813dd6fSViresh Kumar /* Create device specific directory link */ 174a2dea4cbSGreg Kroah-Hartman opp_dev->dentry = debugfs_create_symlink(name, rootdir, 175a2dea4cbSGreg Kroah-Hartman opp_table->dentry_name); 1767813dd6fSViresh Kumar } 1777813dd6fSViresh Kumar 1787813dd6fSViresh Kumar /** 1797813dd6fSViresh Kumar * opp_debug_register - add a device opp node to the debugfs 'opp' directory 1807813dd6fSViresh Kumar * @opp_dev: opp-dev pointer for device 1817813dd6fSViresh Kumar * @opp_table: the device-opp being added 1827813dd6fSViresh Kumar * 1837813dd6fSViresh Kumar * Dynamically adds device specific directory in debugfs 'opp' directory. If the 1847813dd6fSViresh Kumar * device-opp is shared with other devices, then links will be created for all 1857813dd6fSViresh Kumar * devices except the first. 1867813dd6fSViresh Kumar */ 187a2dea4cbSGreg Kroah-Hartman void opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table) 1887813dd6fSViresh Kumar { 1897813dd6fSViresh Kumar if (opp_table->dentry) 190a2dea4cbSGreg Kroah-Hartman opp_list_debug_create_link(opp_dev, opp_table); 191a2dea4cbSGreg Kroah-Hartman else 192a2dea4cbSGreg Kroah-Hartman opp_list_debug_create_dir(opp_dev, opp_table); 1937813dd6fSViresh Kumar } 1947813dd6fSViresh Kumar 1957813dd6fSViresh Kumar static void opp_migrate_dentry(struct opp_device *opp_dev, 1967813dd6fSViresh Kumar struct opp_table *opp_table) 1977813dd6fSViresh Kumar { 1987813dd6fSViresh Kumar struct opp_device *new_dev; 1997813dd6fSViresh Kumar const struct device *dev; 2007813dd6fSViresh Kumar struct dentry *dentry; 2017813dd6fSViresh Kumar 2027813dd6fSViresh Kumar /* Look for next opp-dev */ 2037813dd6fSViresh Kumar list_for_each_entry(new_dev, &opp_table->dev_list, node) 2047813dd6fSViresh Kumar if (new_dev != opp_dev) 2057813dd6fSViresh Kumar break; 2067813dd6fSViresh Kumar 2077813dd6fSViresh Kumar /* new_dev is guaranteed to be valid here */ 2087813dd6fSViresh Kumar dev = new_dev->dev; 2097813dd6fSViresh Kumar debugfs_remove_recursive(new_dev->dentry); 2107813dd6fSViresh Kumar 2117813dd6fSViresh Kumar opp_set_dev_name(dev, opp_table->dentry_name); 2127813dd6fSViresh Kumar 2137813dd6fSViresh Kumar dentry = debugfs_rename(rootdir, opp_dev->dentry, rootdir, 2147813dd6fSViresh Kumar opp_table->dentry_name); 2157813dd6fSViresh Kumar if (!dentry) { 2167813dd6fSViresh Kumar dev_err(dev, "%s: Failed to rename link from: %s to %s\n", 2177813dd6fSViresh Kumar __func__, dev_name(opp_dev->dev), dev_name(dev)); 2187813dd6fSViresh Kumar return; 2197813dd6fSViresh Kumar } 2207813dd6fSViresh Kumar 2217813dd6fSViresh Kumar new_dev->dentry = dentry; 2227813dd6fSViresh Kumar opp_table->dentry = dentry; 2237813dd6fSViresh Kumar } 2247813dd6fSViresh Kumar 2257813dd6fSViresh Kumar /** 2267813dd6fSViresh Kumar * opp_debug_unregister - remove a device opp node from debugfs opp directory 2277813dd6fSViresh Kumar * @opp_dev: opp-dev pointer for device 2287813dd6fSViresh Kumar * @opp_table: the device-opp being removed 2297813dd6fSViresh Kumar * 2307813dd6fSViresh Kumar * Dynamically removes device specific directory from debugfs 'opp' directory. 2317813dd6fSViresh Kumar */ 2327813dd6fSViresh Kumar void opp_debug_unregister(struct opp_device *opp_dev, 2337813dd6fSViresh Kumar struct opp_table *opp_table) 2347813dd6fSViresh Kumar { 2357813dd6fSViresh Kumar if (opp_dev->dentry == opp_table->dentry) { 2367813dd6fSViresh Kumar /* Move the real dentry object under another device */ 2377813dd6fSViresh Kumar if (!list_is_singular(&opp_table->dev_list)) { 2387813dd6fSViresh Kumar opp_migrate_dentry(opp_dev, opp_table); 2397813dd6fSViresh Kumar goto out; 2407813dd6fSViresh Kumar } 2417813dd6fSViresh Kumar opp_table->dentry = NULL; 2427813dd6fSViresh Kumar } 2437813dd6fSViresh Kumar 2447813dd6fSViresh Kumar debugfs_remove_recursive(opp_dev->dentry); 2457813dd6fSViresh Kumar 2467813dd6fSViresh Kumar out: 2477813dd6fSViresh Kumar opp_dev->dentry = NULL; 2487813dd6fSViresh Kumar } 2497813dd6fSViresh Kumar 2507813dd6fSViresh Kumar static int __init opp_debug_init(void) 2517813dd6fSViresh Kumar { 2527813dd6fSViresh Kumar /* Create /sys/kernel/debug/opp directory */ 2537813dd6fSViresh Kumar rootdir = debugfs_create_dir("opp", NULL); 2547813dd6fSViresh Kumar 2557813dd6fSViresh Kumar return 0; 2567813dd6fSViresh Kumar } 2577813dd6fSViresh Kumar core_initcall(opp_debug_init); 258