blob: 936820a9a2cdf73f9d8d4e3a6fb9c2e458a1e455 [file] [log] [blame]
Praveen Chidambaram3895bde2012-05-14 19:42:40 +05301/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 */
13
14#include <linux/module.h>
15#include <linux/kernel.h>
16#include <linux/init.h>
17#include <linux/spinlock.h>
18#include <linux/uaccess.h>
19#include <linux/proc_fs.h>
20
21#include "pm.h"
22
23struct msm_pm_time_stats {
24 const char *name;
25 int64_t first_bucket_time;
26 int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
27 int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
28 int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
29 int count;
30 int64_t total_time;
31 bool enabled;
32};
33
34struct msm_pm_cpu_time_stats {
35 struct msm_pm_time_stats stats[MSM_PM_STAT_COUNT];
36};
37
38static DEFINE_SPINLOCK(msm_pm_stats_lock);
39static DEFINE_PER_CPU_SHARED_ALIGNED(
40 struct msm_pm_cpu_time_stats, msm_pm_stats);
41
42/*
43 * Add the given time data to the statistics collection.
44 */
45void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t)
46{
47 unsigned long flags;
48 struct msm_pm_time_stats *stats;
49 int64_t bt;
50 int i;
51
52 spin_lock_irqsave(&msm_pm_stats_lock, flags);
53 stats = __get_cpu_var(msm_pm_stats).stats;
54
55 if (!stats[id].enabled)
56 goto add_bail;
57
58 stats[id].total_time += t;
59 stats[id].count++;
60
61 bt = t;
62 do_div(bt, stats[id].first_bucket_time);
63
64 if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT *
65 (CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1)))
66 i = DIV_ROUND_UP(fls((uint32_t)bt),
67 CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT);
68 else
69 i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
70
71 if (i >= CONFIG_MSM_IDLE_STATS_BUCKET_COUNT)
72 i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
73
74 stats[id].bucket[i]++;
75
76 if (t < stats[id].min_time[i] || !stats[id].max_time[i])
77 stats[id].min_time[i] = t;
78 if (t > stats[id].max_time[i])
79 stats[id].max_time[i] = t;
80
81add_bail:
82 spin_unlock_irqrestore(&msm_pm_stats_lock, flags);
83}
84
85/*
86 * Helper function of snprintf where buf is auto-incremented, size is auto-
87 * decremented, and there is no return value.
88 *
89 * NOTE: buf and size must be l-values (e.g. variables)
90 */
91#define SNPRINTF(buf, size, format, ...) \
92 do { \
93 if (size > 0) { \
94 int ret; \
95 ret = snprintf(buf, size, format, ## __VA_ARGS__); \
96 if (ret > size) { \
97 buf += size; \
98 size = 0; \
99 } else { \
100 buf += ret; \
101 size -= ret; \
102 } \
103 } \
104 } while (0)
105
106/*
107 * Write out the power management statistics.
108 */
109static int msm_pm_read_proc
110 (char *page, char **start, off_t off, int count, int *eof, void *data)
111{
112 unsigned int cpu = off / MSM_PM_STAT_COUNT;
113 int id = off % MSM_PM_STAT_COUNT;
114 char *p = page;
115
116 if (count < 1024) {
117 *start = (char *) 0;
118 *eof = 0;
119 return 0;
120 }
121
122 if (cpu < num_possible_cpus()) {
123 unsigned long flags;
124 struct msm_pm_time_stats *stats;
125 int i;
126 int64_t bucket_time;
127 int64_t s;
128 uint32_t ns;
129
130 spin_lock_irqsave(&msm_pm_stats_lock, flags);
131 stats = per_cpu(msm_pm_stats, cpu).stats;
132
133 /* Skip the disabled ones */
134 if (!stats[id].enabled) {
135 *p = '\0';
136 p++;
137 goto again;
138 }
139
140 s = stats[id].total_time;
141 ns = do_div(s, NSEC_PER_SEC);
142 SNPRINTF(p, count,
143 "[cpu %u] %s:\n"
144 " count: %7d\n"
145 " total_time: %lld.%09u\n",
146 cpu, stats[id].name,
147 stats[id].count,
148 s, ns);
149
150 bucket_time = stats[id].first_bucket_time;
151 for (i = 0; i < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; i++) {
152 s = bucket_time;
153 ns = do_div(s, NSEC_PER_SEC);
154 SNPRINTF(p, count,
155 " <%6lld.%09u: %7d (%lld-%lld)\n",
156 s, ns, stats[id].bucket[i],
157 stats[id].min_time[i],
158 stats[id].max_time[i]);
159
160 bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT;
161 }
162
163 SNPRINTF(p, count, " >=%6lld.%09u: %7d (%lld-%lld)\n",
164 s, ns, stats[id].bucket[i],
165 stats[id].min_time[i],
166 stats[id].max_time[i]);
167
168again:
169 *start = (char *) 1;
170 *eof = (off + 1 >= MSM_PM_STAT_COUNT * num_possible_cpus());
171
172 spin_unlock_irqrestore(&msm_pm_stats_lock, flags);
173 }
174
175 return p - page;
176}
177#undef SNPRINTF
178
179#define MSM_PM_STATS_RESET "reset"
180
181/*
182 * Reset the power management statistics values.
183 */
184static int msm_pm_write_proc(struct file *file, const char __user *buffer,
185 unsigned long count, void *data)
186{
187 char buf[sizeof(MSM_PM_STATS_RESET)];
188 int ret;
189 unsigned long flags;
190 unsigned int cpu;
191
192 if (count < sizeof(MSM_PM_STATS_RESET)) {
193 ret = -EINVAL;
194 goto write_proc_failed;
195 }
196
197 if (copy_from_user(buf, buffer, sizeof(MSM_PM_STATS_RESET))) {
198 ret = -EFAULT;
199 goto write_proc_failed;
200 }
201
202 if (memcmp(buf, MSM_PM_STATS_RESET, sizeof(MSM_PM_STATS_RESET))) {
203 ret = -EINVAL;
204 goto write_proc_failed;
205 }
206
207 spin_lock_irqsave(&msm_pm_stats_lock, flags);
208 for_each_possible_cpu(cpu) {
209 struct msm_pm_time_stats *stats;
210 int i;
211
212 stats = per_cpu(msm_pm_stats, cpu).stats;
213 for (i = 0; i < MSM_PM_STAT_COUNT; i++) {
214 memset(stats[i].bucket,
215 0, sizeof(stats[i].bucket));
216 memset(stats[i].min_time,
217 0, sizeof(stats[i].min_time));
218 memset(stats[i].max_time,
219 0, sizeof(stats[i].max_time));
220 stats[i].count = 0;
221 stats[i].total_time = 0;
222 }
223 }
224
225 spin_unlock_irqrestore(&msm_pm_stats_lock, flags);
226 return count;
227
228write_proc_failed:
229 return ret;
230}
231#undef MSM_PM_STATS_RESET
232
233void msm_pm_add_stats(enum msm_pm_time_stats_id *enable_stats, int size)
234{
235 unsigned int cpu;
236 struct proc_dir_entry *d_entry;
237 int i = 0;
238
239 for_each_possible_cpu(cpu) {
240 struct msm_pm_time_stats *stats =
241 per_cpu(msm_pm_stats, cpu).stats;
242
243 stats[MSM_PM_STAT_REQUESTED_IDLE].name = "idle-request";
244 stats[MSM_PM_STAT_REQUESTED_IDLE].first_bucket_time =
245 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
246
247 stats[MSM_PM_STAT_IDLE_SPIN].name = "idle-spin";
248 stats[MSM_PM_STAT_IDLE_SPIN].first_bucket_time =
249 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
250
251 stats[MSM_PM_STAT_IDLE_WFI].name = "idle-wfi";
252 stats[MSM_PM_STAT_IDLE_WFI].first_bucket_time =
253 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
254
255 stats[MSM_PM_STAT_RETENTION].name = "retention";
256 stats[MSM_PM_STAT_RETENTION].first_bucket_time =
257 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
258
259 stats[MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE].name =
260 "idle-standalone-power-collapse";
261 stats[MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE].
262 first_bucket_time = CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
263
264 stats[MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE].name =
265 "idle-failed-standalone-power-collapse";
266 stats[MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE].
267 first_bucket_time =
268 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
269
270 stats[MSM_PM_STAT_IDLE_POWER_COLLAPSE].name =
271 "idle-power-collapse";
272 stats[MSM_PM_STAT_IDLE_POWER_COLLAPSE].first_bucket_time =
273 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
274
275 stats[MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].name =
276 "idle-failed-power-collapse";
277 stats[MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].
278 first_bucket_time =
279 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
280
281 stats[MSM_PM_STAT_SUSPEND].name = "suspend";
282 stats[MSM_PM_STAT_SUSPEND].first_bucket_time =
283 CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET;
284
285 stats[MSM_PM_STAT_FAILED_SUSPEND].name = "failed-suspend";
286 stats[MSM_PM_STAT_FAILED_SUSPEND].first_bucket_time =
287 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
288
289 stats[MSM_PM_STAT_NOT_IDLE].name = "not-idle";
290 stats[MSM_PM_STAT_NOT_IDLE].first_bucket_time =
291 CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
292
293 for (i = 0; i < size; i++)
294 stats[enable_stats[i]].enabled = true;
295
296 }
297
298 d_entry = create_proc_entry("msm_pm_stats",
299 S_IRUGO | S_IWUSR | S_IWGRP, NULL);
300 if (d_entry) {
301 d_entry->read_proc = msm_pm_read_proc;
302 d_entry->write_proc = msm_pm_write_proc;
303 d_entry->data = NULL;
304 }
305}