blob: cb8ed1975a049cbbad44ab67e227daf0f6f4c4f5 [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;
Priyanka Mathur953b34a2013-04-11 16:13:30 -070066 u64 accumulated;
67 u32 reserved[4];
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070068};
69
70static inline u64 get_time_in_sec(u64 counter)
71{
72 do_div(counter, MSM_ARCH_TIMER_FREQ);
73 return counter;
74}
75
76static inline u64 get_time_in_msec(u64 counter)
77{
78 do_div(counter, MSM_ARCH_TIMER_FREQ);
79 counter *= MSEC_PER_SEC;
80 return counter;
81}
82
83static inline int msm_rpmstats_append_data_to_buf(char *buf,
84 struct msm_rpm_stats_data_v2 *data, int buflength)
85{
86 char stat_type[5];
87 u64 time_in_last_mode;
88 u64 time_since_last_mode;
Priyanka Mathur953b34a2013-04-11 16:13:30 -070089 u64 actual_last_sleep;
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070090
91 stat_type[4] = 0;
92 memcpy(stat_type, &data->stat_type, sizeof(u32));
93
94 time_in_last_mode = data->last_exited_at - data->last_entered_at;
95 time_in_last_mode = get_time_in_msec(time_in_last_mode);
96 time_since_last_mode = arch_counter_get_cntpct() - data->last_exited_at;
97 time_since_last_mode = get_time_in_sec(time_since_last_mode);
Priyanka Mathur953b34a2013-04-11 16:13:30 -070098 actual_last_sleep = get_time_in_msec(data->accumulated);
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -070099
100 return snprintf(buf , buflength,
101 "RPM Mode:%s\n\t count:%d\n time in last mode(msec):%llu\n"
Priyanka Mathur953b34a2013-04-11 16:13:30 -0700102 "time since last mode(sec):%llu\n actual last sleep(msec):%llu\n",
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700103 stat_type, data->count, time_in_last_mode,
Priyanka Mathur953b34a2013-04-11 16:13:30 -0700104 time_since_last_mode, actual_last_sleep);
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700105}
106
107static inline u32 msm_rpmstats_read_long_register_v2(void __iomem *regbase,
108 int index, int offset)
109{
110 return readl_relaxed(regbase + offset +
111 index * sizeof(struct msm_rpm_stats_data_v2));
112}
113
114static inline u64 msm_rpmstats_read_quad_register_v2(void __iomem *regbase,
115 int index, int offset)
116{
117 u64 dst;
118 memcpy_fromio(&dst,
119 regbase + offset + index * sizeof(struct msm_rpm_stats_data_v2),
120 8);
121 return dst;
122}
123
124static inline int msm_rpmstats_copy_stats_v2(
125 struct msm_rpmstats_private_data *prvdata)
126{
127 void __iomem *reg;
128 struct msm_rpm_stats_data_v2 data;
129 int i, length;
130
131 reg = prvdata->reg_base;
132
133 for (i = 0, length = 0; i < prvdata->num_records; i++) {
134
135 data.stat_type = msm_rpmstats_read_long_register_v2(reg, i,
136 offsetof(struct msm_rpm_stats_data_v2,
137 stat_type));
138 data.count = msm_rpmstats_read_long_register_v2(reg, i,
139 offsetof(struct msm_rpm_stats_data_v2, count));
140 data.last_entered_at = msm_rpmstats_read_quad_register_v2(reg,
141 i, offsetof(struct msm_rpm_stats_data_v2,
142 last_entered_at));
143 data.last_exited_at = msm_rpmstats_read_quad_register_v2(reg,
144 i, offsetof(struct msm_rpm_stats_data_v2,
145 last_exited_at));
146
Priyanka Mathur953b34a2013-04-11 16:13:30 -0700147 data.accumulated = msm_rpmstats_read_quad_register_v2(reg,
148 i, offsetof(struct msm_rpm_stats_data_v2,
149 accumulated));
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700150 length += msm_rpmstats_append_data_to_buf(prvdata->buf + length,
151 &data, sizeof(prvdata->buf) - length);
152 prvdata->read_idx++;
153 }
154 return length;
155}
156
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700157static inline unsigned long msm_rpmstats_read_register(void __iomem *regbase,
158 int index, int offset)
159{
160 return readl_relaxed(regbase + index * 12 + (offset + 1) * 4);
161}
162static void msm_rpmstats_strcpy(char *dest, char *src)
163{
164 union {
165 char ch[4];
166 unsigned long word;
167 } string;
168 int index = 0;
169
170 do {
171 int i;
172 string.word = readl_relaxed(src + 4 * index);
173 for (i = 0; i < 4; i++) {
174 *dest++ = string.ch[i];
175 if (!string.ch[i])
176 break;
177 }
178 index++;
179 } while (*(dest-1));
180
181}
182static int msm_rpmstats_copy_stats(struct msm_rpmstats_private_data *pdata)
183{
184
185 struct msm_rpmstats_record record;
186 unsigned long ptr;
187 unsigned long offset;
188 char *str;
189 uint64_t usec;
190
191 ptr = msm_rpmstats_read_register(pdata->reg_base, pdata->read_idx, 0);
192 offset = (ptr - (unsigned long)pdata->platform_data->phys_addr_base);
193
194 if (offset > pdata->platform_data->phys_size)
195 str = (char *)ioremap(ptr, SZ_256);
196 else
197 str = (char *) pdata->reg_base + offset;
198
199 msm_rpmstats_strcpy(record.name, str);
200
201 if (offset > pdata->platform_data->phys_size)
202 iounmap(str);
203
204 record.id = msm_rpmstats_read_register(pdata->reg_base,
205 pdata->read_idx, 1);
206 record.val = msm_rpmstats_read_register(pdata->reg_base,
207 pdata->read_idx, 2);
208
209 if (record.id == ID_ACCUM_TIME_SCLK) {
210 usec = record.val * USEC_PER_SEC;
211 do_div(usec, SCLK_HZ);
212 } else
213 usec = (unsigned long)record.val;
214
215 pdata->read_idx++;
216
217 return snprintf(pdata->buf, sizeof(pdata->buf),
218 "RPM Mode:%s\n\t%s:%llu\n",
219 record.name,
220 msm_rpmstats_id_labels[record.id],
221 usec);
222}
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700223
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700224static int msm_rpmstats_file_read(struct file *file, char __user *bufu,
225 size_t count, loff_t *ppos)
226{
227 struct msm_rpmstats_private_data *prvdata;
228 prvdata = file->private_data;
229
230 if (!prvdata)
231 return -EINVAL;
232
Anji Jonnala538c19f2012-12-26 14:03:12 +0530233 if (!bufu || count == 0)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700234 return -EINVAL;
235
Priyanka Mathur71859f42012-10-17 10:54:35 -0700236 if (prvdata->platform_data->version == 1) {
237 if (!prvdata->num_records)
238 prvdata->num_records = readl_relaxed(prvdata->reg_base);
239 }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700240
241 if ((*ppos >= prvdata->len)
Priyanka Mathur71859f42012-10-17 10:54:35 -0700242 && (prvdata->read_idx < prvdata->num_records)) {
243 if (prvdata->platform_data->version == 1)
244 prvdata->len = msm_rpmstats_copy_stats(prvdata);
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700245 else if (prvdata->platform_data->version == 2)
246 prvdata->len = msm_rpmstats_copy_stats_v2(
247 prvdata);
Priyanka Mathur71859f42012-10-17 10:54:35 -0700248 *ppos = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700249 }
250
251 return simple_read_from_buffer(bufu, count, ppos,
252 prvdata->buf, prvdata->len);
253}
254
255static int msm_rpmstats_file_open(struct inode *inode, struct file *file)
256{
257 struct msm_rpmstats_private_data *prvdata;
258 struct msm_rpmstats_platform_data *pdata;
259
260 pdata = inode->i_private;
261
262 file->private_data =
263 kmalloc(sizeof(struct msm_rpmstats_private_data), GFP_KERNEL);
264
265 if (!file->private_data)
266 return -ENOMEM;
267 prvdata = file->private_data;
268
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700269 prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base,
270 pdata->phys_size);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700271 if (!prvdata->reg_base) {
272 kfree(file->private_data);
273 prvdata = NULL;
274 pr_err("%s: ERROR could not ioremap start=%p, len=%u\n",
275 __func__, (void *)pdata->phys_addr_base,
276 pdata->phys_size);
277 return -EBUSY;
278 }
279
280 prvdata->read_idx = prvdata->num_records = prvdata->len = 0;
281 prvdata->platform_data = pdata;
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700282 if (pdata->version == 2)
283 prvdata->num_records = 2;
284
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700285 return 0;
286}
287
288static int msm_rpmstats_file_close(struct inode *inode, struct file *file)
289{
290 struct msm_rpmstats_private_data *private = file->private_data;
291
292 if (private->reg_base)
293 iounmap(private->reg_base);
294 kfree(file->private_data);
295
296 return 0;
297}
298
299static const struct file_operations msm_rpmstats_fops = {
300 .owner = THIS_MODULE,
301 .open = msm_rpmstats_file_open,
302 .read = msm_rpmstats_file_read,
303 .release = msm_rpmstats_file_close,
304 .llseek = no_llseek,
305};
306
307static int __devinit msm_rpmstats_probe(struct platform_device *pdev)
308{
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700309 struct dentry *dent = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700310 struct msm_rpmstats_platform_data *pdata;
Priyanka Mathur71859f42012-10-17 10:54:35 -0700311 struct msm_rpmstats_platform_data *pd;
312 struct resource *res = NULL;
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700313 struct device_node *node = NULL;
314 int ret = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700315
Priyanka Mathur71859f42012-10-17 10:54:35 -0700316 if (!pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700317 return -EINVAL;
Priyanka Mathur71859f42012-10-17 10:54:35 -0700318
319 pdata = kzalloc(sizeof(struct msm_rpmstats_platform_data), GFP_KERNEL);
320
321 if (!pdata)
322 return -ENOMEM;
323
324 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
325
326 if (!res)
327 return -EINVAL;
328
329 pdata->phys_addr_base = res->start;
330
331 pdata->phys_size = resource_size(res);
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700332 node = pdev->dev.of_node;
Priyanka Mathur71859f42012-10-17 10:54:35 -0700333 if (pdev->dev.platform_data) {
334 pd = pdev->dev.platform_data;
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700335 pdata->version = pd->version;
Priyanka Mathur71859f42012-10-17 10:54:35 -0700336
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700337 } else if (node)
338 ret = of_property_read_u32(node,
339 "qcom,sleep-stats-version", &pdata->version);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700340
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700341 if (!ret) {
342
343 dent = debugfs_create_file("rpm_stats", S_IRUGO, NULL,
344 pdata, &msm_rpmstats_fops);
345
346 if (!dent) {
347 pr_err("%s: ERROR debugfs_create_file failed\n",
348 __func__);
349 kfree(pdata);
350 return -ENOMEM;
351 }
352
353 } else {
Priyanka Mathur71859f42012-10-17 10:54:35 -0700354 kfree(pdata);
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700355 return -EINVAL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700356 }
357 platform_set_drvdata(pdev, dent);
358 return 0;
359}
360
361static int __devexit msm_rpmstats_remove(struct platform_device *pdev)
362{
363 struct dentry *dent;
364
365 dent = platform_get_drvdata(pdev);
366 debugfs_remove(dent);
367 platform_set_drvdata(pdev, NULL);
368 return 0;
369}
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700370
371static struct of_device_id rpm_stats_table[] = {
372 {.compatible = "qcom,rpm-stats"},
373 {},
374};
375
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700376static struct platform_driver msm_rpmstats_driver = {
377 .probe = msm_rpmstats_probe,
378 .remove = __devexit_p(msm_rpmstats_remove),
379 .driver = {
380 .name = "msm_rpm_stat",
381 .owner = THIS_MODULE,
Priyanka Mathurc9b9b3c2012-10-18 18:37:33 -0700382 .of_match_table = rpm_stats_table,
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700383 },
384};
385static int __init msm_rpmstats_init(void)
386{
387 return platform_driver_register(&msm_rpmstats_driver);
388}
389static void __exit msm_rpmstats_exit(void)
390{
391 platform_driver_unregister(&msm_rpmstats_driver);
392}
393module_init(msm_rpmstats_init);
394module_exit(msm_rpmstats_exit);
395
396MODULE_LICENSE("GPL v2");
397MODULE_DESCRIPTION("MSM RPM Statistics driver");
398MODULE_VERSION("1.0");
399MODULE_ALIAS("platform:msm_stat_log");