17813dd6fSViresh Kumar /* 27813dd6fSViresh Kumar * Generic OPP debugfs interface 37813dd6fSViresh Kumar * 47813dd6fSViresh Kumar * Copyright (C) 2015-2016 Viresh Kumar <viresh.kumar@linaro.org> 57813dd6fSViresh Kumar * 67813dd6fSViresh Kumar * This program is free software; you can redistribute it and/or modify 77813dd6fSViresh Kumar * it under the terms of the GNU General Public License version 2 as 87813dd6fSViresh Kumar * published by the Free Software Foundation. 97813dd6fSViresh Kumar */ 107813dd6fSViresh Kumar 117813dd6fSViresh Kumar #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 127813dd6fSViresh Kumar 137813dd6fSViresh Kumar #include <linux/debugfs.h> 147813dd6fSViresh Kumar #include <linux/device.h> 157813dd6fSViresh Kumar #include <linux/err.h> 167813dd6fSViresh Kumar #include <linux/init.h> 177813dd6fSViresh Kumar #include <linux/limits.h> 187813dd6fSViresh Kumar #include <linux/slab.h> 197813dd6fSViresh Kumar 207813dd6fSViresh Kumar #include "opp.h" 217813dd6fSViresh Kumar 227813dd6fSViresh Kumar static struct dentry *rootdir; 237813dd6fSViresh Kumar 247813dd6fSViresh Kumar static void opp_set_dev_name(const struct device *dev, char *name) 257813dd6fSViresh Kumar { 267813dd6fSViresh Kumar if (dev->parent) 277813dd6fSViresh Kumar snprintf(name, NAME_MAX, "%s-%s", dev_name(dev->parent), 287813dd6fSViresh Kumar dev_name(dev)); 297813dd6fSViresh Kumar else 307813dd6fSViresh Kumar snprintf(name, NAME_MAX, "%s", dev_name(dev)); 317813dd6fSViresh Kumar } 327813dd6fSViresh Kumar 337813dd6fSViresh Kumar void opp_debug_remove_one(struct dev_pm_opp *opp) 347813dd6fSViresh Kumar { 357813dd6fSViresh Kumar debugfs_remove_recursive(opp->dentry); 367813dd6fSViresh Kumar } 377813dd6fSViresh Kumar 387813dd6fSViresh Kumar static bool opp_debug_create_supplies(struct dev_pm_opp *opp, 397813dd6fSViresh Kumar struct opp_table *opp_table, 407813dd6fSViresh Kumar struct dentry *pdentry) 417813dd6fSViresh Kumar { 427813dd6fSViresh Kumar struct dentry *d; 437813dd6fSViresh Kumar int i; 447813dd6fSViresh Kumar char *name; 457813dd6fSViresh Kumar 467813dd6fSViresh Kumar for (i = 0; i < opp_table->regulator_count; i++) { 477813dd6fSViresh Kumar name = kasprintf(GFP_KERNEL, "supply-%d", i); 487813dd6fSViresh Kumar 497813dd6fSViresh Kumar /* Create per-opp directory */ 507813dd6fSViresh Kumar d = debugfs_create_dir(name, pdentry); 517813dd6fSViresh Kumar 527813dd6fSViresh Kumar kfree(name); 537813dd6fSViresh Kumar 547813dd6fSViresh Kumar if (!d) 557813dd6fSViresh Kumar return false; 567813dd6fSViresh Kumar 577813dd6fSViresh Kumar if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, 587813dd6fSViresh Kumar &opp->supplies[i].u_volt)) 597813dd6fSViresh Kumar return false; 607813dd6fSViresh Kumar 617813dd6fSViresh Kumar if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, 627813dd6fSViresh Kumar &opp->supplies[i].u_volt_min)) 637813dd6fSViresh Kumar return false; 647813dd6fSViresh Kumar 657813dd6fSViresh Kumar if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, 667813dd6fSViresh Kumar &opp->supplies[i].u_volt_max)) 677813dd6fSViresh Kumar return false; 687813dd6fSViresh Kumar 697813dd6fSViresh Kumar if (!debugfs_create_ulong("u_amp", S_IRUGO, d, 707813dd6fSViresh Kumar &opp->supplies[i].u_amp)) 717813dd6fSViresh Kumar return false; 727813dd6fSViresh Kumar } 737813dd6fSViresh Kumar 747813dd6fSViresh Kumar return true; 757813dd6fSViresh Kumar } 767813dd6fSViresh Kumar 777813dd6fSViresh Kumar int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) 787813dd6fSViresh Kumar { 797813dd6fSViresh Kumar struct dentry *pdentry = opp_table->dentry; 807813dd6fSViresh Kumar struct dentry *d; 817813dd6fSViresh Kumar char name[25]; /* 20 chars for 64 bit value + 5 (opp:\0) */ 827813dd6fSViresh Kumar 837813dd6fSViresh Kumar /* Rate is unique to each OPP, use it to give opp-name */ 847813dd6fSViresh Kumar snprintf(name, sizeof(name), "opp:%lu", opp->rate); 857813dd6fSViresh Kumar 867813dd6fSViresh Kumar /* Create per-opp directory */ 877813dd6fSViresh Kumar d = debugfs_create_dir(name, pdentry); 887813dd6fSViresh Kumar if (!d) 897813dd6fSViresh Kumar return -ENOMEM; 907813dd6fSViresh Kumar 917813dd6fSViresh Kumar if (!debugfs_create_bool("available", S_IRUGO, d, &opp->available)) 927813dd6fSViresh Kumar return -ENOMEM; 937813dd6fSViresh Kumar 947813dd6fSViresh Kumar if (!debugfs_create_bool("dynamic", S_IRUGO, d, &opp->dynamic)) 957813dd6fSViresh Kumar return -ENOMEM; 967813dd6fSViresh Kumar 977813dd6fSViresh Kumar if (!debugfs_create_bool("turbo", S_IRUGO, d, &opp->turbo)) 987813dd6fSViresh Kumar return -ENOMEM; 997813dd6fSViresh Kumar 1007813dd6fSViresh Kumar if (!debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend)) 1017813dd6fSViresh Kumar return -ENOMEM; 1027813dd6fSViresh Kumar 1037813dd6fSViresh Kumar if (!debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate)) 1047813dd6fSViresh Kumar return -ENOMEM; 1057813dd6fSViresh Kumar 1067813dd6fSViresh Kumar if (!opp_debug_create_supplies(opp, opp_table, d)) 1077813dd6fSViresh Kumar return -ENOMEM; 1087813dd6fSViresh Kumar 1097813dd6fSViresh Kumar if (!debugfs_create_ulong("clock_latency_ns", S_IRUGO, d, 1107813dd6fSViresh Kumar &opp->clock_latency_ns)) 1117813dd6fSViresh Kumar return -ENOMEM; 1127813dd6fSViresh Kumar 1137813dd6fSViresh Kumar opp->dentry = d; 1147813dd6fSViresh Kumar return 0; 1157813dd6fSViresh Kumar } 1167813dd6fSViresh Kumar 1177813dd6fSViresh Kumar static int opp_list_debug_create_dir(struct opp_device *opp_dev, 1187813dd6fSViresh Kumar struct opp_table *opp_table) 1197813dd6fSViresh Kumar { 1207813dd6fSViresh Kumar const struct device *dev = opp_dev->dev; 1217813dd6fSViresh Kumar struct dentry *d; 1227813dd6fSViresh Kumar 1237813dd6fSViresh Kumar opp_set_dev_name(dev, opp_table->dentry_name); 1247813dd6fSViresh Kumar 1257813dd6fSViresh Kumar /* Create device specific directory */ 1267813dd6fSViresh Kumar d = debugfs_create_dir(opp_table->dentry_name, rootdir); 1277813dd6fSViresh Kumar if (!d) { 1287813dd6fSViresh Kumar dev_err(dev, "%s: Failed to create debugfs dir\n", __func__); 1297813dd6fSViresh Kumar return -ENOMEM; 1307813dd6fSViresh Kumar } 1317813dd6fSViresh Kumar 1327813dd6fSViresh Kumar opp_dev->dentry = d; 1337813dd6fSViresh Kumar opp_table->dentry = d; 1347813dd6fSViresh Kumar 1357813dd6fSViresh Kumar return 0; 1367813dd6fSViresh Kumar } 1377813dd6fSViresh Kumar 1387813dd6fSViresh Kumar static int opp_list_debug_create_link(struct opp_device *opp_dev, 1397813dd6fSViresh Kumar struct opp_table *opp_table) 1407813dd6fSViresh Kumar { 1417813dd6fSViresh Kumar const struct device *dev = opp_dev->dev; 1427813dd6fSViresh Kumar char name[NAME_MAX]; 1437813dd6fSViresh Kumar struct dentry *d; 1447813dd6fSViresh Kumar 1457813dd6fSViresh Kumar opp_set_dev_name(opp_dev->dev, name); 1467813dd6fSViresh Kumar 1477813dd6fSViresh Kumar /* Create device specific directory link */ 1487813dd6fSViresh Kumar d = debugfs_create_symlink(name, rootdir, opp_table->dentry_name); 1497813dd6fSViresh Kumar if (!d) { 1507813dd6fSViresh Kumar dev_err(dev, "%s: Failed to create link\n", __func__); 1517813dd6fSViresh Kumar return -ENOMEM; 1527813dd6fSViresh Kumar } 1537813dd6fSViresh Kumar 1547813dd6fSViresh Kumar opp_dev->dentry = d; 1557813dd6fSViresh Kumar 1567813dd6fSViresh Kumar return 0; 1577813dd6fSViresh Kumar } 1587813dd6fSViresh Kumar 1597813dd6fSViresh Kumar /** 1607813dd6fSViresh Kumar * opp_debug_register - add a device opp node to the debugfs 'opp' directory 1617813dd6fSViresh Kumar * @opp_dev: opp-dev pointer for device 1627813dd6fSViresh Kumar * @opp_table: the device-opp being added 1637813dd6fSViresh Kumar * 1647813dd6fSViresh Kumar * Dynamically adds device specific directory in debugfs 'opp' directory. If the 1657813dd6fSViresh Kumar * device-opp is shared with other devices, then links will be created for all 1667813dd6fSViresh Kumar * devices except the first. 1677813dd6fSViresh Kumar * 1687813dd6fSViresh Kumar * Return: 0 on success, otherwise negative error. 1697813dd6fSViresh Kumar */ 1707813dd6fSViresh Kumar int opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table) 1717813dd6fSViresh Kumar { 1727813dd6fSViresh Kumar if (!rootdir) { 1737813dd6fSViresh Kumar pr_debug("%s: Uninitialized rootdir\n", __func__); 1747813dd6fSViresh Kumar return -EINVAL; 1757813dd6fSViresh Kumar } 1767813dd6fSViresh Kumar 1777813dd6fSViresh Kumar if (opp_table->dentry) 1787813dd6fSViresh Kumar return opp_list_debug_create_link(opp_dev, opp_table); 1797813dd6fSViresh Kumar 1807813dd6fSViresh Kumar return opp_list_debug_create_dir(opp_dev, opp_table); 1817813dd6fSViresh Kumar } 1827813dd6fSViresh Kumar 1837813dd6fSViresh Kumar static void opp_migrate_dentry(struct opp_device *opp_dev, 1847813dd6fSViresh Kumar struct opp_table *opp_table) 1857813dd6fSViresh Kumar { 1867813dd6fSViresh Kumar struct opp_device *new_dev; 1877813dd6fSViresh Kumar const struct device *dev; 1887813dd6fSViresh Kumar struct dentry *dentry; 1897813dd6fSViresh Kumar 1907813dd6fSViresh Kumar /* Look for next opp-dev */ 1917813dd6fSViresh Kumar list_for_each_entry(new_dev, &opp_table->dev_list, node) 1927813dd6fSViresh Kumar if (new_dev != opp_dev) 1937813dd6fSViresh Kumar break; 1947813dd6fSViresh Kumar 1957813dd6fSViresh Kumar /* new_dev is guaranteed to be valid here */ 1967813dd6fSViresh Kumar dev = new_dev->dev; 1977813dd6fSViresh Kumar debugfs_remove_recursive(new_dev->dentry); 1987813dd6fSViresh Kumar 1997813dd6fSViresh Kumar opp_set_dev_name(dev, opp_table->dentry_name); 2007813dd6fSViresh Kumar 2017813dd6fSViresh Kumar dentry = debugfs_rename(rootdir, opp_dev->dentry, rootdir, 2027813dd6fSViresh Kumar opp_table->dentry_name); 2037813dd6fSViresh Kumar if (!dentry) { 2047813dd6fSViresh Kumar dev_err(dev, "%s: Failed to rename link from: %s to %s\n", 2057813dd6fSViresh Kumar __func__, dev_name(opp_dev->dev), dev_name(dev)); 2067813dd6fSViresh Kumar return; 2077813dd6fSViresh Kumar } 2087813dd6fSViresh Kumar 2097813dd6fSViresh Kumar new_dev->dentry = dentry; 2107813dd6fSViresh Kumar opp_table->dentry = dentry; 2117813dd6fSViresh Kumar } 2127813dd6fSViresh Kumar 2137813dd6fSViresh Kumar /** 2147813dd6fSViresh Kumar * opp_debug_unregister - remove a device opp node from debugfs opp directory 2157813dd6fSViresh Kumar * @opp_dev: opp-dev pointer for device 2167813dd6fSViresh Kumar * @opp_table: the device-opp being removed 2177813dd6fSViresh Kumar * 2187813dd6fSViresh Kumar * Dynamically removes device specific directory from debugfs 'opp' directory. 2197813dd6fSViresh Kumar */ 2207813dd6fSViresh Kumar void opp_debug_unregister(struct opp_device *opp_dev, 2217813dd6fSViresh Kumar struct opp_table *opp_table) 2227813dd6fSViresh Kumar { 2237813dd6fSViresh Kumar if (opp_dev->dentry == opp_table->dentry) { 2247813dd6fSViresh Kumar /* Move the real dentry object under another device */ 2257813dd6fSViresh Kumar if (!list_is_singular(&opp_table->dev_list)) { 2267813dd6fSViresh Kumar opp_migrate_dentry(opp_dev, opp_table); 2277813dd6fSViresh Kumar goto out; 2287813dd6fSViresh Kumar } 2297813dd6fSViresh Kumar opp_table->dentry = NULL; 2307813dd6fSViresh Kumar } 2317813dd6fSViresh Kumar 2327813dd6fSViresh Kumar debugfs_remove_recursive(opp_dev->dentry); 2337813dd6fSViresh Kumar 2347813dd6fSViresh Kumar out: 2357813dd6fSViresh Kumar opp_dev->dentry = NULL; 2367813dd6fSViresh Kumar } 2377813dd6fSViresh Kumar 2387813dd6fSViresh Kumar static int __init opp_debug_init(void) 2397813dd6fSViresh Kumar { 2407813dd6fSViresh Kumar /* Create /sys/kernel/debug/opp directory */ 2417813dd6fSViresh Kumar rootdir = debugfs_create_dir("opp", NULL); 2427813dd6fSViresh Kumar if (!rootdir) { 2437813dd6fSViresh Kumar pr_err("%s: Failed to create root directory\n", __func__); 2447813dd6fSViresh Kumar return -ENOMEM; 2457813dd6fSViresh Kumar } 2467813dd6fSViresh Kumar 2477813dd6fSViresh Kumar return 0; 2487813dd6fSViresh Kumar } 2497813dd6fSViresh Kumar core_initcall(opp_debug_init); 250