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 | ||
37 | int opp_debug_create_one(struct dev_pm_opp *opp, struct device_opp *dev_opp) | |
38 | { | |
39 | struct dentry *pdentry = dev_opp->dentry; | |
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 | ||
86 | static int device_opp_debug_create_dir(struct device_list_opp *list_dev, | |
87 | struct device_opp *dev_opp) | |
88 | { | |
89 | const struct device *dev = list_dev->dev; | |
90 | struct dentry *d; | |
91 | ||
92 | opp_set_dev_name(dev, dev_opp->dentry_name); | |
93 | ||
94 | /* Create device specific directory */ | |
95 | d = debugfs_create_dir(dev_opp->dentry_name, rootdir); | |
96 | if (!d) { | |
97 | dev_err(dev, "%s: Failed to create debugfs dir\n", __func__); | |
98 | return -ENOMEM; | |
99 | } | |
100 | ||
101 | list_dev->dentry = d; | |
102 | dev_opp->dentry = d; | |
103 | ||
104 | return 0; | |
105 | } | |
106 | ||
107 | static int device_opp_debug_create_link(struct device_list_opp *list_dev, | |
108 | struct device_opp *dev_opp) | |
109 | { | |
110 | const struct device *dev = list_dev->dev; | |
111 | char name[NAME_MAX]; | |
112 | struct dentry *d; | |
113 | ||
114 | opp_set_dev_name(list_dev->dev, name); | |
115 | ||
116 | /* Create device specific directory link */ | |
117 | d = debugfs_create_symlink(name, rootdir, dev_opp->dentry_name); | |
118 | if (!d) { | |
119 | dev_err(dev, "%s: Failed to create link\n", __func__); | |
120 | return -ENOMEM; | |
121 | } | |
122 | ||
123 | list_dev->dentry = d; | |
124 | ||
125 | return 0; | |
126 | } | |
127 | ||
128 | /** | |
129 | * opp_debug_register - add a device opp node to the debugfs 'opp' directory | |
130 | * @list_dev: list-dev pointer for device | |
131 | * @dev_opp: the device-opp being added | |
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 | */ | |
139 | int opp_debug_register(struct device_list_opp *list_dev, | |
140 | struct device_opp *dev_opp) | |
141 | { | |
142 | if (!rootdir) { | |
143 | pr_debug("%s: Uninitialized rootdir\n", __func__); | |
144 | return -EINVAL; | |
145 | } | |
146 | ||
147 | if (dev_opp->dentry) | |
148 | return device_opp_debug_create_link(list_dev, dev_opp); | |
149 | ||
150 | return device_opp_debug_create_dir(list_dev, dev_opp); | |
151 | } | |
152 | ||
153 | static void opp_migrate_dentry(struct device_list_opp *list_dev, | |
154 | struct device_opp *dev_opp) | |
155 | { | |
156 | struct device_list_opp *new_dev; | |
157 | const struct device *dev; | |
158 | struct dentry *dentry; | |
159 | ||
160 | /* Look for next list-dev */ | |
161 | list_for_each_entry(new_dev, &dev_opp->dev_list, node) | |
162 | if (new_dev != list_dev) | |
163 | break; | |
164 | ||
165 | /* new_dev is guaranteed to be valid here */ | |
166 | dev = new_dev->dev; | |
167 | debugfs_remove_recursive(new_dev->dentry); | |
168 | ||
169 | opp_set_dev_name(dev, dev_opp->dentry_name); | |
170 | ||
171 | dentry = debugfs_rename(rootdir, list_dev->dentry, rootdir, | |
172 | dev_opp->dentry_name); | |
173 | if (!dentry) { | |
174 | dev_err(dev, "%s: Failed to rename link from: %s to %s\n", | |
175 | __func__, dev_name(list_dev->dev), dev_name(dev)); | |
176 | return; | |
177 | } | |
178 | ||
179 | new_dev->dentry = dentry; | |
180 | dev_opp->dentry = dentry; | |
181 | } | |
182 | ||
183 | /** | |
184 | * opp_debug_unregister - remove a device opp node from debugfs opp directory | |
185 | * @list_dev: list-dev pointer for device | |
186 | * @dev_opp: the device-opp being removed | |
187 | * | |
188 | * Dynamically removes device specific directory from debugfs 'opp' directory. | |
189 | */ | |
190 | void opp_debug_unregister(struct device_list_opp *list_dev, | |
191 | struct device_opp *dev_opp) | |
192 | { | |
193 | if (list_dev->dentry == dev_opp->dentry) { | |
194 | /* Move the real dentry object under another device */ | |
195 | if (!list_is_singular(&dev_opp->dev_list)) { | |
196 | opp_migrate_dentry(list_dev, dev_opp); | |
197 | goto out; | |
198 | } | |
199 | dev_opp->dentry = NULL; | |
200 | } | |
201 | ||
202 | debugfs_remove_recursive(list_dev->dentry); | |
203 | ||
204 | out: | |
205 | list_dev->dentry = NULL; | |
206 | } | |
207 | ||
208 | static int __init opp_debug_init(void) | |
209 | { | |
210 | /* Create /sys/kernel/debug/opp directory */ | |
211 | rootdir = debugfs_create_dir("opp", NULL); | |
212 | if (!rootdir) { | |
213 | pr_err("%s: Failed to create root directory\n", __func__); | |
214 | return -ENOMEM; | |
215 | } | |
216 | ||
217 | return 0; | |
218 | } | |
219 | core_initcall(opp_debug_init); |