1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Generic OPP debugfs interface 4 * 5 * Copyright (C) 2015-2016 Viresh Kumar <viresh.kumar@linaro.org> 6 */ 7 8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 9 10 #include <linux/debugfs.h> 11 #include <linux/device.h> 12 #include <linux/err.h> 13 #include <linux/init.h> 14 #include <linux/limits.h> 15 #include <linux/slab.h> 16 17 #include "opp.h" 18 19 static struct dentry *rootdir; 20 21 static void opp_set_dev_name(const struct device *dev, char *name) 22 { 23 if (dev->parent) 24 snprintf(name, NAME_MAX, "%s-%s", dev_name(dev->parent), 25 dev_name(dev)); 26 else 27 snprintf(name, NAME_MAX, "%s", dev_name(dev)); 28 } 29 30 void opp_debug_remove_one(struct dev_pm_opp *opp) 31 { 32 debugfs_remove_recursive(opp->dentry); 33 } 34 35 static ssize_t bw_name_read(struct file *fp, char __user *userbuf, 36 size_t count, loff_t *ppos) 37 { 38 struct icc_path *path = fp->private_data; 39 char buf[64]; 40 int i; 41 42 i = scnprintf(buf, sizeof(buf), "%.62s\n", icc_get_name(path)); 43 44 return simple_read_from_buffer(userbuf, count, ppos, buf, i); 45 } 46 47 static const struct file_operations bw_name_fops = { 48 .open = simple_open, 49 .read = bw_name_read, 50 .llseek = default_llseek, 51 }; 52 53 static void opp_debug_create_bw(struct dev_pm_opp *opp, 54 struct opp_table *opp_table, 55 struct dentry *pdentry) 56 { 57 struct dentry *d; 58 char name[11]; 59 int i; 60 61 for (i = 0; i < opp_table->path_count; i++) { 62 snprintf(name, sizeof(name), "icc-path-%.1d", i); 63 64 /* Create per-path directory */ 65 d = debugfs_create_dir(name, pdentry); 66 67 debugfs_create_file("name", S_IRUGO, d, opp_table->paths[i], 68 &bw_name_fops); 69 debugfs_create_u32("peak_bw", S_IRUGO, d, 70 &opp->bandwidth[i].peak); 71 debugfs_create_u32("avg_bw", S_IRUGO, d, 72 &opp->bandwidth[i].avg); 73 } 74 } 75 76 static void opp_debug_create_supplies(struct dev_pm_opp *opp, 77 struct opp_table *opp_table, 78 struct dentry *pdentry) 79 { 80 struct dentry *d; 81 int i; 82 83 for (i = 0; i < opp_table->regulator_count; i++) { 84 char name[15]; 85 86 snprintf(name, sizeof(name), "supply-%d", i); 87 88 /* Create per-opp directory */ 89 d = debugfs_create_dir(name, pdentry); 90 91 debugfs_create_ulong("u_volt_target", S_IRUGO, d, 92 &opp->supplies[i].u_volt); 93 94 debugfs_create_ulong("u_volt_min", S_IRUGO, d, 95 &opp->supplies[i].u_volt_min); 96 97 debugfs_create_ulong("u_volt_max", S_IRUGO, d, 98 &opp->supplies[i].u_volt_max); 99 100 debugfs_create_ulong("u_amp", S_IRUGO, d, 101 &opp->supplies[i].u_amp); 102 } 103 } 104 105 void opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) 106 { 107 struct dentry *pdentry = opp_table->dentry; 108 struct dentry *d; 109 unsigned long id; 110 char name[25]; /* 20 chars for 64 bit value + 5 (opp:\0) */ 111 112 /* 113 * Get directory name for OPP. 114 * 115 * - Normally rate is unique to each OPP, use it to get unique opp-name. 116 * - For some devices rate isn't available, use index instead. 117 */ 118 if (likely(opp->rate)) 119 id = opp->rate; 120 else 121 id = _get_opp_count(opp_table); 122 123 snprintf(name, sizeof(name), "opp:%lu", id); 124 125 /* Create per-opp directory */ 126 d = debugfs_create_dir(name, pdentry); 127 128 debugfs_create_bool("available", S_IRUGO, d, &opp->available); 129 debugfs_create_bool("dynamic", S_IRUGO, d, &opp->dynamic); 130 debugfs_create_bool("turbo", S_IRUGO, d, &opp->turbo); 131 debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend); 132 debugfs_create_u32("performance_state", S_IRUGO, d, &opp->pstate); 133 debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate); 134 debugfs_create_ulong("clock_latency_ns", S_IRUGO, d, 135 &opp->clock_latency_ns); 136 137 opp_debug_create_supplies(opp, opp_table, d); 138 opp_debug_create_bw(opp, opp_table, d); 139 140 opp->dentry = d; 141 } 142 143 static void opp_list_debug_create_dir(struct opp_device *opp_dev, 144 struct opp_table *opp_table) 145 { 146 const struct device *dev = opp_dev->dev; 147 struct dentry *d; 148 149 opp_set_dev_name(dev, opp_table->dentry_name); 150 151 /* Create device specific directory */ 152 d = debugfs_create_dir(opp_table->dentry_name, rootdir); 153 154 opp_dev->dentry = d; 155 opp_table->dentry = d; 156 } 157 158 static void opp_list_debug_create_link(struct opp_device *opp_dev, 159 struct opp_table *opp_table) 160 { 161 char name[NAME_MAX]; 162 163 opp_set_dev_name(opp_dev->dev, name); 164 165 /* Create device specific directory link */ 166 opp_dev->dentry = debugfs_create_symlink(name, rootdir, 167 opp_table->dentry_name); 168 } 169 170 /** 171 * opp_debug_register - add a device opp node to the debugfs 'opp' directory 172 * @opp_dev: opp-dev pointer for device 173 * @opp_table: the device-opp being added 174 * 175 * Dynamically adds device specific directory in debugfs 'opp' directory. If the 176 * device-opp is shared with other devices, then links will be created for all 177 * devices except the first. 178 */ 179 void opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table) 180 { 181 if (opp_table->dentry) 182 opp_list_debug_create_link(opp_dev, opp_table); 183 else 184 opp_list_debug_create_dir(opp_dev, opp_table); 185 } 186 187 static void opp_migrate_dentry(struct opp_device *opp_dev, 188 struct opp_table *opp_table) 189 { 190 struct opp_device *new_dev; 191 const struct device *dev; 192 struct dentry *dentry; 193 194 /* Look for next opp-dev */ 195 list_for_each_entry(new_dev, &opp_table->dev_list, node) 196 if (new_dev != opp_dev) 197 break; 198 199 /* new_dev is guaranteed to be valid here */ 200 dev = new_dev->dev; 201 debugfs_remove_recursive(new_dev->dentry); 202 203 opp_set_dev_name(dev, opp_table->dentry_name); 204 205 dentry = debugfs_rename(rootdir, opp_dev->dentry, rootdir, 206 opp_table->dentry_name); 207 if (!dentry) { 208 dev_err(dev, "%s: Failed to rename link from: %s to %s\n", 209 __func__, dev_name(opp_dev->dev), dev_name(dev)); 210 return; 211 } 212 213 new_dev->dentry = dentry; 214 opp_table->dentry = dentry; 215 } 216 217 /** 218 * opp_debug_unregister - remove a device opp node from debugfs opp directory 219 * @opp_dev: opp-dev pointer for device 220 * @opp_table: the device-opp being removed 221 * 222 * Dynamically removes device specific directory from debugfs 'opp' directory. 223 */ 224 void opp_debug_unregister(struct opp_device *opp_dev, 225 struct opp_table *opp_table) 226 { 227 if (opp_dev->dentry == opp_table->dentry) { 228 /* Move the real dentry object under another device */ 229 if (!list_is_singular(&opp_table->dev_list)) { 230 opp_migrate_dentry(opp_dev, opp_table); 231 goto out; 232 } 233 opp_table->dentry = NULL; 234 } 235 236 debugfs_remove_recursive(opp_dev->dentry); 237 238 out: 239 opp_dev->dentry = NULL; 240 } 241 242 static int __init opp_debug_init(void) 243 { 244 /* Create /sys/kernel/debug/opp directory */ 245 rootdir = debugfs_create_dir("opp", NULL); 246 247 return 0; 248 } 249 core_initcall(opp_debug_init); 250