blob: 176c3de30b3c4e69ef7c425914280919ac4cd1d1 [file] [log] [blame]
Priyanka Mathur71859f42012-10-17 10:54:35 -07001/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
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#include <linux/debugfs.h>
14#include <linux/delay.h>
15#include <linux/errno.h>
16#include <linux/init.h>
17#include <linux/io.h>
18#include <linux/kernel.h>
19#include <linux/module.h>
20#include <linux/platform_device.h>
21#include <linux/sched.h>
22#include <linux/slab.h>
23#include <linux/types.h>
24#include <linux/mm.h>
Priyanka Mathur71859f42012-10-17 10:54:35 -070025#include <linux/types.h>
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070026#include <linux/of.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070027#include <asm/uaccess.h>
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070028#include <asm/arch_timer.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070029#include <mach/msm_iomap.h>
30#include "rpm_stats.h"
31
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070032
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070033enum {
34 ID_COUNTER,
35 ID_ACCUM_TIME_SCLK,
36 ID_MAX,
37};
38
39static char *msm_rpmstats_id_labels[ID_MAX] = {
40 [ID_COUNTER] = "Count",
41 [ID_ACCUM_TIME_SCLK] = "Total time(uSec)",
42};
43
44#define SCLK_HZ 32768
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070045#define MSM_ARCH_TIMER_FREQ 19200000
46
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070047struct msm_rpmstats_record{
48 char name[32];
49 uint32_t id;
50 uint32_t val;
51};
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070052struct msm_rpmstats_private_data{
53 void __iomem *reg_base;
54 u32 num_records;
55 u32 read_idx;
56 u32 len;
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070057 char buf[256];
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070058 struct msm_rpmstats_platform_data *platform_data;
59};
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070060
61struct msm_rpm_stats_data_v2 {
62 u32 stat_type;
63 u32 count;
64 u64 last_entered_at;
65 u64 last_exited_at;
66};
67
68static inline u64 get_time_in_sec(u64 counter)
69{
70 do_div(counter, MSM_ARCH_TIMER_FREQ);
71 return counter;
72}
73
74static inline u64 get_time_in_msec(u64 counter)
75{
76 do_div(counter, MSM_ARCH_TIMER_FREQ);
77 counter *= MSEC_PER_SEC;
78 return counter;
79}
80
81static inline int msm_rpmstats_append_data_to_buf(char *buf,
82 struct msm_rpm_stats_data_v2 *data, int buflength)
83{
84 char stat_type[5];
85 u64 time_in_last_mode;
86 u64 time_since_last_mode;
87
88 stat_type[4] = 0;
89 memcpy(stat_type, &data->stat_type, sizeof(u32));
90
91 time_in_last_mode = data->last_exited_at - data->last_entered_at;
92 time_in_last_mode = get_time_in_msec(time_in_last_mode);
93 time_since_last_mode = arch_counter_get_cntpct() - data->last_exited_at;
94 time_since_last_mode = get_time_in_sec(time_since_last_mode);
95
96 return snprintf(buf , buflength,
97 "RPM Mode:%s\n\t count:%d\n time in last mode(msec):%llu\n"
98 "time since last mode(sec):%llu\n",
99 stat_type, data->count, time_in_last_mode,
100 time_since_last_mode);
101}
102
103static inline u32 msm_rpmstats_read_long_register_v2(void __iomem *regbase,
104 int index, int offset)
105{
106 return readl_relaxed(regbase + offset +
107 index * sizeof(struct msm_rpm_stats_data_v2));
108}
109
110static inline u64 msm_rpmstats_read_quad_register_v2(void __iomem *regbase,
111 int index, int offset)
112{
113 u64 dst;
114 memcpy_fromio(&dst,
115 regbase + offset + index * sizeof(struct msm_rpm_stats_data_v2),
116 8);
117 return dst;
118}
119
120static inline int msm_rpmstats_copy_stats_v2(
121 struct msm_rpmstats_private_data *prvdata)
122{
123 void __iomem *reg;
124 struct msm_rpm_stats_data_v2 data;
125 int i, length;
126
127 reg = prvdata->reg_base;
128
129 for (i = 0, length = 0; i < prvdata->num_records; i++) {
130
131 data.stat_type = msm_rpmstats_read_long_register_v2(reg, i,
132 offsetof(struct msm_rpm_stats_data_v2,
133 stat_type));
134 data.count = msm_rpmstats_read_long_register_v2(reg, i,
135 offsetof(struct msm_rpm_stats_data_v2, count));
136 data.last_entered_at = msm_rpmstats_read_quad_register_v2(reg,
137 i, offsetof(struct msm_rpm_stats_data_v2,
138 last_entered_at));
139 data.last_exited_at = msm_rpmstats_read_quad_register_v2(reg,
140 i, offsetof(struct msm_rpm_stats_data_v2,
141 last_exited_at));
142
143 length += msm_rpmstats_append_data_to_buf(prvdata->buf + length,
144 &data, sizeof(prvdata->buf) - length);
145 prvdata->read_idx++;
146 }
147 return length;
148}
149
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700150static inline unsigned long msm_rpmstats_read_register(void __iomem *regbase,
151 int index, int offset)
152{
153 return readl_relaxed(regbase + index * 12 + (offset + 1) * 4);
154}
155static void msm_rpmstats_strcpy(char *dest, char *src)
156{
157 union {
158 char ch[4];
159 unsigned long word;
160 } string;
161 int index = 0;
162
163 do {
164 int i;
165 string.word = readl_relaxed(src + 4 * index);
166 for (i = 0; i < 4; i++) {
167 *dest++ = string.ch[i];
168 if (!string.ch[i])
169 break;
170 }
171 index++;
172 } while (*(dest-1));
173
174}
175static int msm_rpmstats_copy_stats(struct msm_rpmstats_private_data *pdata)
176{
177
178 struct msm_rpmstats_record record;
179 unsigned long ptr;
180 unsigned long offset;
181 char *str;
182 uint64_t usec;
183
184 ptr = msm_rpmstats_read_register(pdata->reg_base, pdata->read_idx, 0);
185 offset = (ptr - (unsigned long)pdata->platform_data->phys_addr_base);
186
187 if (offset > pdata->platform_data->phys_size)
188 str = (char *)ioremap(ptr, SZ_256);
189 else
190 str = (char *) pdata->reg_base + offset;
191
192 msm_rpmstats_strcpy(record.name, str);
193
194 if (offset > pdata->platform_data->phys_size)
195 iounmap(str);
196
197 record.id = msm_rpmstats_read_register(pdata->reg_base,
198 pdata->read_idx, 1);
199 record.val = msm_rpmstats_read_register(pdata->reg_base,
200 pdata->read_idx, 2);
201
202 if (record.id == ID_ACCUM_TIME_SCLK) {
203 usec = record.val * USEC_PER_SEC;
204 do_div(usec, SCLK_HZ);
205 } else
206 usec = (unsigned long)record.val;
207
208 pdata->read_idx++;
209
210 return snprintf(pdata->buf, sizeof(pdata->buf),
211 "RPM Mode:%s\n\t%s:%llu\n",
212 record.name,
213 msm_rpmstats_id_labels[record.id],
214 usec);
215}
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700216
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700217static int msm_rpmstats_file_read(struct file *file, char __user *bufu,
218 size_t count, loff_t *ppos)
219{
220 struct msm_rpmstats_private_data *prvdata;
221 prvdata = file->private_data;
222
223 if (!prvdata)
224 return -EINVAL;
225
Anji Jonnala538c19f2012-12-26 14:03:12 +0530226 if (!bufu || count == 0)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700227 return -EINVAL;
228
Priyanka Mathur71859f42012-10-17 10:54:35 -0700229 if (prvdata->platform_data->version == 1) {
230 if (!prvdata->num_records)
231 prvdata->num_records = readl_relaxed(prvdata->reg_base);
232 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700233
234 if ((*ppos >= prvdata->len)
Priyanka Mathur71859f42012-10-17 10:54:35 -0700235 && (prvdata->read_idx < prvdata->num_records)) {
236 if (prvdata->platform_data->version == 1)
237 prvdata->len = msm_rpmstats_copy_stats(prvdata);
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700238 else if (prvdata->platform_data->version == 2)
239 prvdata->len = msm_rpmstats_copy_stats_v2(
240 prvdata);
Priyanka Mathur71859f42012-10-17 10:54:35 -0700241 *ppos = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700242 }
243
244 return simple_read_from_buffer(bufu, count, ppos,
245 prvdata->buf, prvdata->len);
246}
247
248static int msm_rpmstats_file_open(struct inode *inode, struct file *file)
249{
250 struct msm_rpmstats_private_data *prvdata;
251 struct msm_rpmstats_platform_data *pdata;
252
253 pdata = inode->i_private;
254
255 file->private_data =
256 kmalloc(sizeof(struct msm_rpmstats_private_data), GFP_KERNEL);
257
258 if (!file->private_data)
259 return -ENOMEM;
260 prvdata = file->private_data;
261
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700262 prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base,
263 pdata->phys_size);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700264 if (!prvdata->reg_base) {
265 kfree(file->private_data);
266 prvdata = NULL;
267 pr_err("%s: ERROR could not ioremap start=%p, len=%u\n",
268 __func__, (void *)pdata->phys_addr_base,
269 pdata->phys_size);
270 return -EBUSY;
271 }
272
273 prvdata->read_idx = prvdata->num_records = prvdata->len = 0;
274 prvdata->platform_data = pdata;
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700275 if (pdata->version == 2)
276 prvdata->num_records = 2;
277
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700278 return 0;
279}
280
281static int msm_rpmstats_file_close(struct inode *inode, struct file *file)
282{
283 struct msm_rpmstats_private_data *private = file->private_data;
284
285 if (private->reg_base)
286 iounmap(private->reg_base);
287 kfree(file->private_data);
288
289 return 0;
290}
291
292static const struct file_operations msm_rpmstats_fops = {
293 .owner = THIS_MODULE,
294 .open = msm_rpmstats_file_open,
295 .read = msm_rpmstats_file_read,
296 .release = msm_rpmstats_file_close,
297 .llseek = no_llseek,
298};
299
300static int __devinit msm_rpmstats_probe(struct platform_device *pdev)
301{
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700302 struct dentry *dent = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700303 struct msm_rpmstats_platform_data *pdata;
Priyanka Mathur71859f42012-10-17 10:54:35 -0700304 struct msm_rpmstats_platform_data *pd;
305 struct resource *res = NULL;
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700306 struct device_node *node = NULL;
307 int ret = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700308
Priyanka Mathur71859f42012-10-17 10:54:35 -0700309 if (!pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700310 return -EINVAL;
Priyanka Mathur71859f42012-10-17 10:54:35 -0700311
312 pdata = kzalloc(sizeof(struct msm_rpmstats_platform_data), GFP_KERNEL);
313
314 if (!pdata)
315 return -ENOMEM;
316
317 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
318
319 if (!res)
320 return -EINVAL;
321
322 pdata->phys_addr_base = res->start;
323
324 pdata->phys_size = resource_size(res);
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700325 node = pdev->dev.of_node;
Priyanka Mathur71859f42012-10-17 10:54:35 -0700326 if (pdev->dev.platform_data) {
327 pd = pdev->dev.platform_data;
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700328 pdata->version = pd->version;
Priyanka Mathur71859f42012-10-17 10:54:35 -0700329
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700330 } else if (node)
331 ret = of_property_read_u32(node,
332 "qcom,sleep-stats-version", &pdata->version);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700333
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700334 if (!ret) {
335
336 dent = debugfs_create_file("rpm_stats", S_IRUGO, NULL,
337 pdata, &msm_rpmstats_fops);
338
339 if (!dent) {
340 pr_err("%s: ERROR debugfs_create_file failed\n",
341 __func__);
342 kfree(pdata);
343 return -ENOMEM;
344 }
345
346 } else {
Priyanka Mathur71859f42012-10-17 10:54:35 -0700347 kfree(pdata);
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700348 return -EINVAL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700349 }
350 platform_set_drvdata(pdev, dent);
351 return 0;
352}
353
354static int __devexit msm_rpmstats_remove(struct platform_device *pdev)
355{
356 struct dentry *dent;
357
358 dent = platform_get_drvdata(pdev);
359 debugfs_remove(dent);
360 platform_set_drvdata(pdev, NULL);
361 return 0;
362}
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700363
364static struct of_device_id rpm_stats_table[] = {
365 {.compatible = "qcom,rpm-stats"},
366 {},
367};
368
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700369static struct platform_driver msm_rpmstats_driver = {
370 .probe = msm_rpmstats_probe,
371 .remove = __devexit_p(msm_rpmstats_remove),
372 .driver = {
373 .name = "msm_rpm_stat",
374 .owner = THIS_MODULE,
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700375 .of_match_table = rpm_stats_table,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700376 },
377};
378static int __init msm_rpmstats_init(void)
379{
380 return platform_driver_register(&msm_rpmstats_driver);
381}
382static void __exit msm_rpmstats_exit(void)
383{
384 platform_driver_unregister(&msm_rpmstats_driver);
385}
386module_init(msm_rpmstats_init);
387module_exit(msm_rpmstats_exit);
388
389MODULE_LICENSE("GPL v2");
390MODULE_DESCRIPTION("MSM RPM Statistics driver");
391MODULE_VERSION("1.0");
392MODULE_ALIAS("platform:msm_stat_log");