Commit | Line | Data |
---|---|---|
deaa5146 VK |
1 | /* |
2 | * Generic OPP debugfs interface | |
3 | * | |
4 | * Copyright (C) 2015-2016 Viresh Kumar <viresh.kumar@linaro.org> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
10 | ||
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
12 | ||
13 | #include <linux/debugfs.h> | |
14 | #include <linux/device.h> | |
15 | #include <linux/err.h> | |
16 | #include <linux/init.h> | |
17 | #include <linux/limits.h> | |
18 | ||
19 | #include "opp.h" | |
20 | ||
21 | static struct dentry *rootdir; | |
22 | ||
23 | static void opp_set_dev_name(const struct device *dev, char *name) | |
24 | { | |
25 | if (dev->parent) | |
26 | snprintf(name, NAME_MAX, "%s-%s", dev_name(dev->parent), | |
27 | dev_name(dev)); | |
28 | else | |
29 | snprintf(name, NAME_MAX, "%s", dev_name(dev)); | |
30 | } | |
31 | ||
32 | void opp_debug_remove_one(struct dev_pm_opp *opp) | |
33 | { | |
34 | debugfs_remove_recursive(opp->dentry); | |
35 | } | |
36 | ||
2c2709dc | 37 | int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) |
deaa5146 | 38 | { |
2c2709dc | 39 | struct dentry *pdentry = opp_table->dentry; |
deaa5146 VK |
40 | struct dentry *d; |
41 | char name[25]; /* 20 chars for 64 bit value + 5 (opp:\0) */ | |
42 | ||
43 | /* Rate is unique to each OPP, use it to give opp-name */ | |
44 | snprintf(name, sizeof(name), "opp:%lu", opp->rate); | |
45 | ||
46 | /* Create per-opp directory */ | |
47 | d = debugfs_create_dir(name, pdentry); | |
48 | if (!d) | |
49 | return -ENOMEM; | |
50 | ||
51 | if (!debugfs_create_bool("available", S_IRUGO, d, &opp->available)) | |
52 | return -ENOMEM; | |
53 | ||
54 | if (!debugfs_create_bool("dynamic", S_IRUGO, d, &opp->dynamic)) | |
55 | return -ENOMEM; | |
56 | ||
57 | if (!debugfs_create_bool("turbo", S_IRUGO, d, &opp->turbo)) | |
58 | return -ENOMEM; | |
59 | ||
60 | if (!debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend)) | |
61 | return -ENOMEM; | |
62 | ||
63 | if (!debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate)) | |
64 | return -ENOMEM; | |
65 | ||
66 | if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->u_volt)) | |
67 | return -ENOMEM; | |
68 | ||
69 | if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->u_volt_min)) | |
70 | return -ENOMEM; | |
71 | ||
72 | if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->u_volt_max)) | |
73 | return -ENOMEM; | |
74 | ||
75 | if (!debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->u_amp)) | |
76 | return -ENOMEM; | |
77 | ||
78 | if (!debugfs_create_ulong("clock_latency_ns", S_IRUGO, d, | |
79 | &opp->clock_latency_ns)) | |
80 | return -ENOMEM; | |
81 | ||
82 | opp->dentry = d; | |
83 | return 0; | |
84 | } | |
85 | ||
2c2709dc VK |
86 | static int opp_list_debug_create_dir(struct opp_device *opp_dev, |
87 | struct opp_table *opp_table) | |
deaa5146 | 88 | { |
2c2709dc | 89 | const struct device *dev = opp_dev->dev; |
deaa5146 VK |
90 | struct dentry *d; |
91 | ||
2c2709dc | 92 | opp_set_dev_name(dev, opp_table->dentry_name); |
deaa5146 VK |
93 | |
94 | /* Create device specific directory */ | |
2c2709dc | 95 | d = debugfs_create_dir(opp_table->dentry_name, rootdir); |
deaa5146 VK |
96 | if (!d) { |
97 | dev_err(dev, "%s: Failed to create debugfs dir\n", __func__); | |
98 | return -ENOMEM; | |
99 | } | |
100 | ||
2c2709dc VK |
101 | opp_dev->dentry = d; |
102 | opp_table->dentry = d; | |
deaa5146 VK |
103 | |
104 | return 0; | |
105 | } | |
106 | ||
2c2709dc VK |
107 | static int opp_list_debug_create_link(struct opp_device *opp_dev, |
108 | struct opp_table *opp_table) | |
deaa5146 | 109 | { |
2c2709dc | 110 | const struct device *dev = opp_dev->dev; |
deaa5146 VK |
111 | char name[NAME_MAX]; |
112 | struct dentry *d; | |
113 | ||
2c2709dc | 114 | opp_set_dev_name(opp_dev->dev, name); |
deaa5146 VK |
115 | |
116 | /* Create device specific directory link */ | |
2c2709dc | 117 | d = debugfs_create_symlink(name, rootdir, opp_table->dentry_name); |
deaa5146 VK |
118 | if (!d) { |
119 | dev_err(dev, "%s: Failed to create link\n", __func__); | |
120 | return -ENOMEM; | |
121 | } | |
122 | ||
2c2709dc | 123 | opp_dev->dentry = d; |
deaa5146 VK |
124 | |
125 | return 0; | |
126 | } | |
127 | ||
128 | /** | |
129 | * opp_debug_register - add a device opp node to the debugfs 'opp' directory | |
2c2709dc VK |
130 | * @opp_dev: opp-dev pointer for device |
131 | * @opp_table: the device-opp being added | |
deaa5146 VK |
132 | * |
133 | * Dynamically adds device specific directory in debugfs 'opp' directory. If the | |
134 | * device-opp is shared with other devices, then links will be created for all | |
135 | * devices except the first. | |
136 | * | |
137 | * Return: 0 on success, otherwise negative error. | |
138 | */ | |
2c2709dc | 139 | int opp_debug_register(struct opp_device *opp_dev, struct opp_table *opp_table) |
deaa5146 VK |
140 | { |
141 | if (!rootdir) { | |
142 | pr_debug("%s: Uninitialized rootdir\n", __func__); | |
143 | return -EINVAL; | |
144 | } | |
145 | ||
2c2709dc VK |
146 | if (opp_table->dentry) |
147 | return opp_list_debug_create_link(opp_dev, opp_table); | |
deaa5146 | 148 | |
2c2709dc | 149 | return opp_list_debug_create_dir(opp_dev, opp_table); |
deaa5146 VK |
150 | } |
151 | ||
2c2709dc VK |
152 | static void opp_migrate_dentry(struct opp_device *opp_dev, |
153 | struct opp_table *opp_table) | |
deaa5146 | 154 | { |
2c2709dc | 155 | struct opp_device *new_dev; |
deaa5146 VK |
156 | const struct device *dev; |
157 | struct dentry *dentry; | |
158 | ||
2c2709dc VK |
159 | /* Look for next opp-dev */ |
160 | list_for_each_entry(new_dev, &opp_table->dev_list, node) | |
161 | if (new_dev != opp_dev) | |
deaa5146 VK |
162 | break; |
163 | ||
164 | /* new_dev is guaranteed to be valid here */ | |
165 | dev = new_dev->dev; | |
166 | debugfs_remove_recursive(new_dev->dentry); | |
167 | ||
2c2709dc | 168 | opp_set_dev_name(dev, opp_table->dentry_name); |
deaa5146 | 169 | |
2c2709dc VK |
170 | dentry = debugfs_rename(rootdir, opp_dev->dentry, rootdir, |
171 | opp_table->dentry_name); | |
deaa5146 VK |
172 | if (!dentry) { |
173 | dev_err(dev, "%s: Failed to rename link from: %s to %s\n", | |
2c2709dc | 174 | __func__, dev_name(opp_dev->dev), dev_name(dev)); |
deaa5146 VK |
175 | return; |
176 | } | |
177 | ||
178 | new_dev->dentry = dentry; | |
2c2709dc | 179 | opp_table->dentry = dentry; |
deaa5146 VK |
180 | } |
181 | ||
182 | /** | |
183 | * opp_debug_unregister - remove a device opp node from debugfs opp directory | |
2c2709dc VK |
184 | * @opp_dev: opp-dev pointer for device |
185 | * @opp_table: the device-opp being removed | |
deaa5146 VK |
186 | * |
187 | * Dynamically removes device specific directory from debugfs 'opp' directory. | |
188 | */ | |
2c2709dc VK |
189 | void opp_debug_unregister(struct opp_device *opp_dev, |
190 | struct opp_table *opp_table) | |
deaa5146 | 191 | { |
2c2709dc | 192 | if (opp_dev->dentry == opp_table->dentry) { |
deaa5146 | 193 | /* Move the real dentry object under another device */ |
2c2709dc VK |
194 | if (!list_is_singular(&opp_table->dev_list)) { |
195 | opp_migrate_dentry(opp_dev, opp_table); | |
deaa5146 VK |
196 | goto out; |
197 | } | |
2c2709dc | 198 | opp_table->dentry = NULL; |
deaa5146 VK |
199 | } |
200 | ||
2c2709dc | 201 | debugfs_remove_recursive(opp_dev->dentry); |
deaa5146 VK |
202 | |
203 | out: | |
2c2709dc | 204 | opp_dev->dentry = NULL; |
deaa5146 VK |
205 | } |
206 | ||
207 | static int __init opp_debug_init(void) | |
208 | { | |
209 | /* Create /sys/kernel/debug/opp directory */ | |
210 | rootdir = debugfs_create_dir("opp", NULL); | |
211 | if (!rootdir) { | |
212 | pr_err("%s: Failed to create root directory\n", __func__); | |
213 | return -ENOMEM; | |
214 | } | |
215 | ||
216 | return 0; | |
217 | } | |
218 | core_initcall(opp_debug_init); |