blob: bfa25706f1e8eaae68851822de782117834e3c5e [file] [log] [blame]
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -06001/* Copyright (c) 2013, The Linux Foundation. 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/debugfs.h>
15#include <linux/delay.h>
16#include <linux/errno.h>
17#include <linux/init.h>
18#include <linux/io.h>
19#include <linux/kernel.h>
20#include <linux/module.h>
21#include <linux/of.h>
22#include <linux/platform_device.h>
23#include <linux/sched.h>
24#include <linux/slab.h>
25#include <linux/types.h>
26#include <linux/mm.h>
27#include <linux/mutex.h>
28#include <linux/sort.h>
29#include <asm/uaccess.h>
30#include <mach/msm_iomap.h>
31
32#define RBCPR_BUF_LEN 8000
33#define RBCPR_STATS_MAX_SIZE SZ_2K
34#define RBCPR_MAX_RAILS 4
35#define RBCPR_NUM_RECMNDS 3
36#define RBCPR_NUM_CORNERS 3
37
38#define FIELD(a) ((strnstr(#a, "->", 80) + 2))
39#define PRINT(buf, pos, format, ...) \
40 ((pos < RBCPR_BUF_LEN) ? snprintf((buf + pos), (RBCPR_BUF_LEN - pos),\
41 format, ## __VA_ARGS__) : 0)
42
43enum {
44 CORNER_OFF,
Venkat Devarasettyb31780f2013-10-08 18:33:53 +053045 CORNER_RETENTION,
46 CORNER_SVS_KRAIT,
47 CORNER_SVS_SOC,
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -060048 CORNER_NOMINAL,
49 CORNER_TURBO,
Venkat Devarasettyb31780f2013-10-08 18:33:53 +053050 CORNER_SUPER_TURBO,
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -060051 CORNER_MAX,
52};
53
54struct rbcpr_recmnd_data_type {
55 uint32_t microvolts;
56 uint64_t timestamp;
57};
58
59struct rbcpr_corners_data_type {
Venkat Devarasettyb31780f2013-10-08 18:33:53 +053060 int32_t efuse_adjustment;
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -060061 uint32_t programmed_voltage;
62 uint32_t isr_count;
63 uint32_t min_count;
64 uint32_t max_count;
65 struct rbcpr_recmnd_data_type rbcpr_recmnd[RBCPR_NUM_RECMNDS];
66};
67
Venkat Devarasettyb31780f2013-10-08 18:33:53 +053068struct rbcpr_rail_stats_header_type {
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -060069 uint32_t num_corners;
70 uint32_t num_latest_recommends;
Venkat Devarasettyb31780f2013-10-08 18:33:53 +053071};
72
73struct rbcpr_rail_stats_footer_type {
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -060074 uint32_t current_corner;
75 uint32_t railway_voltage;
76 uint32_t off_corner;
77 uint32_t margin;
78};
79
80struct rbcpr_stats_type {
81 uint32_t num_rails;
82 uint32_t status;
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -060083};
84
85struct rbcpr_data_type {
86 void __iomem *start;
87 uint32_t len;
88 char buf[RBCPR_BUF_LEN];
89};
90
91static char *rbcpr_rail_labels[] = {
92 [0] = "VDD-CX",
93 [1] = "VDD-GFX",
94};
95
96static char *rbcpr_corner_string[] = {
97 [CORNER_OFF] = "CORNERS_OFF",
Venkat Devarasettyb31780f2013-10-08 18:33:53 +053098 [CORNER_RETENTION] = "RETENTION",
99 [CORNER_SVS_KRAIT] = "SVS",
100 [CORNER_SVS_SOC] = "SVS_SOC",
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600101 [CORNER_NOMINAL] = "NOMINAL",
102 [CORNER_TURBO] = "TURBO",
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530103 [CORNER_SUPER_TURBO] = "SUPER_TURBO",
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600104};
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530105
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600106#define CORNER_STRING(a) \
107 ((a >= CORNER_MAX) ? "INVALID Corner" : rbcpr_corner_string[a])
108
109static struct rbcpr_data_type *rbcpr_data;
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600110
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530111static void msm_rpmrbcpr_print_stats_header(
112 struct rbcpr_stats_type *rbcpr_stats, char *buf,
113 uint32_t *pos)
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600114{
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530115 *pos += PRINT(buf, *pos, "\n:RBCPR STATS ");
116 *pos += PRINT(buf, *pos, "(%s: %d)", FIELD(rbcpr_stats->num_rails),
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600117 rbcpr_stats->num_rails);
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530118 *pos += PRINT(buf, *pos, "(%s: %d)", FIELD(rbcpr_stats->status),
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600119 rbcpr_stats->status);
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600120}
121
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530122static void msm_rpmrbcpr_print_rail_header(
123 struct rbcpr_rail_stats_header_type *rail_header, char *buf,
124 uint32_t *pos)
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600125{
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530126 *pos += PRINT(buf, *pos, "(%s: %d)", FIELD(rail_header->num_corners),
127 rail_header->num_corners);
128 *pos += PRINT(buf, *pos, "(%s: %d)",
129 FIELD(rail_header->num_latest_recommends),
130 rail_header->num_latest_recommends);
131}
132
133static void msm_rpmrbcpr_print_corner_recmnds(
134 struct rbcpr_recmnd_data_type *rbcpr_recmnd, char *buf,
135 uint32_t *pos)
136{
137 *pos += PRINT(buf, *pos, "\n\t\t\t :(%s: %d) ",
138 FIELD(rbcpr_recmd->microvolts),
139 rbcpr_recmnd->microvolts);
140 *pos += PRINT(buf, *pos, " (%s: %lld)", FIELD(rbcpr_recmd->timestamp),
141 rbcpr_recmnd->timestamp);
142}
143
144static void msm_rpmrbcpr_print_corner_data(
145 struct rbcpr_corners_data_type *corner, char *buf,
146 uint32_t num_corners, uint32_t *pos)
147{
148 int i;
149
150 *pos += PRINT(buf, *pos, "(%s: %d)",
151 FIELD(corner->efuse_adjustment),
152 corner->efuse_adjustment);
153 *pos += PRINT(buf, *pos, "(%s: %d)",
154 FIELD(corner->programmed_voltage),
155 corner->programmed_voltage);
156 *pos += PRINT(buf, *pos, "(%s: %d)",
157 FIELD(corner->isr_count), corner->isr_count);
158 *pos += PRINT(buf, *pos, "(%s: %d)",
159 FIELD(corner->min_count), corner->min_count);
160 *pos += PRINT(buf, *pos, "(%s: %d)\n",
161 FIELD(corner->max_count), corner->max_count);
162 *pos += PRINT(buf, *pos, "\t\t\t:Latest Recommends");
163 for (i = 0; i < num_corners; i++)
164 msm_rpmrbcpr_print_corner_recmnds(&corner->rbcpr_recmnd[i], buf,
165 pos);
166}
167
168static void msm_rpmrbcpr_print_rail_footer(
169 struct rbcpr_rail_stats_footer_type *rail, char *buf,
170 uint32_t *pos)
171{
172 *pos += PRINT(buf, *pos, "(%s: %s)", FIELD(rail->current_corner),
173 CORNER_STRING(rail->current_corner));
174 *pos += PRINT(buf, *pos, "(%s: %d)",
175 FIELD(rail->railway_voltage), rail->railway_voltage);
176 *pos += PRINT(buf, *pos, "(%s: %d)",
177 FIELD(rail->off_corner), rail->off_corner);
178 *pos += PRINT(buf, *pos, "(%s: %d)\n",
179 FIELD(rail->margin), rail->margin);
180}
181
182static uint32_t msm_rpmrbcpr_read_rpm_data(void)
183{
184 uint32_t read_offset = 0;
185 static struct rbcpr_stats_type rbcpr_stats_header;
186 uint32_t buffer_offset = 0;
187 char *buf = rbcpr_data->buf;
188 int i, j;
189
190 memcpy_fromio(&rbcpr_stats_header, rbcpr_data->start,
191 sizeof(rbcpr_stats_header));
192 read_offset += sizeof(rbcpr_stats_header);
193 msm_rpmrbcpr_print_stats_header(&rbcpr_stats_header, buf,
194 &buffer_offset);
195
196 for (i = 0; i < rbcpr_stats_header.num_rails; i++) {
197 static struct rbcpr_rail_stats_header_type rail_header;
198 static struct rbcpr_rail_stats_footer_type rail_footer;
199
200 memcpy_fromio(&rail_header, (rbcpr_data->start + read_offset),
201 sizeof(rail_header));
202 read_offset += sizeof(rail_header);
203 buffer_offset += PRINT(buf, buffer_offset, "\n:%s Rail Data ",
204 rbcpr_rail_labels[i]);
205 msm_rpmrbcpr_print_rail_header(&rail_header, buf,
206 &buffer_offset);
207
208 for (j = 0; j < rail_header.num_corners; j++) {
209 static struct rbcpr_corners_data_type corner;
210 uint32_t corner_index;
211
212 memcpy_fromio(&corner,
213 (rbcpr_data->start + read_offset),
214 sizeof(corner));
215 read_offset += sizeof(corner);
216
217 /*
218 * RPM doesn't include corner type in the data for the
219 * corner. For now add this hack to know which corners
220 * are used based on number of corners for the rail.
221 */
222 corner_index = j + 3;
223 if (rail_header.num_corners == 3 && j == 2)
224 corner_index++;
225
226 buffer_offset += PRINT(buf, buffer_offset,
227 "\n\t\t:Corner Data: %s ",
228 CORNER_STRING(corner_index));
229 msm_rpmrbcpr_print_corner_data(&corner, buf,
230 rail_header.num_latest_recommends,
231 &buffer_offset);
232 }
233 buffer_offset += PRINT(buf, buffer_offset,
234 "\n\t\t");
235 memcpy_fromio(&rail_footer, (rbcpr_data->start + read_offset),
236 sizeof(rail_footer));
237 read_offset += sizeof(rail_footer);
238 msm_rpmrbcpr_print_rail_footer(&rail_footer, buf,
239 &buffer_offset);
240 }
241 return buffer_offset;
242}
243
244static int msm_rpmrbcpr_file_read(struct seq_file *m, void *data)
245{
246 struct rbcpr_data_type *pdata = m->private;
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600247 int ret = 0;
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530248 int curr_status_counter;
249 static int prev_status_counter;
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600250 static DEFINE_MUTEX(rbcpr_lock);
251
252 mutex_lock(&rbcpr_lock);
253 if (!pdata) {
254 pr_err("%s pdata is null", __func__);
255 ret = -EINVAL;
256 goto exit_rpmrbcpr_file_read;
257 }
258
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530259 /* Read RPM stats */
260 curr_status_counter = readl_relaxed(pdata->start +
261 offsetof(struct rbcpr_stats_type, status));
262 if (curr_status_counter != prev_status_counter) {
263 pdata->len = msm_rpmrbcpr_read_rpm_data();
264 pdata->len = 0;
265 prev_status_counter = curr_status_counter;
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600266 }
267
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530268 seq_printf(m, "%s", pdata->buf);
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600269
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600270exit_rpmrbcpr_file_read:
271 mutex_unlock(&rbcpr_lock);
272 return ret;
273}
274
275static int msm_rpmrbcpr_file_open(struct inode *inode, struct file *file)
276{
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600277 if (!rbcpr_data->start)
278 return -ENODEV;
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530279 return single_open(file, msm_rpmrbcpr_file_read, inode->i_private);
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600280}
281
282static const struct file_operations msm_rpmrbcpr_fops = {
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530283 .open = msm_rpmrbcpr_file_open,
284 .read = seq_read,
285 .llseek = seq_lseek,
286 .release = single_release,
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600287};
288
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530289static int msm_rpmrbcpr_validate(struct platform_device *pdev)
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600290{
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600291 int ret = 0;
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530292 uint32_t num_rails;
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600293
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530294 num_rails = readl_relaxed(rbcpr_data->start);
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600295
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530296 if (num_rails > RBCPR_MAX_RAILS) {
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600297 pr_err("%s: Invalid number of RPM RBCPR rails %d",
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530298 __func__, num_rails);
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600299 ret = -EFAULT;
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600300 }
301
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600302 return ret;
303}
304
305static int __devinit msm_rpmrbcpr_probe(struct platform_device *pdev)
306{
307 struct dentry *dent;
308 int ret = 0;
309 struct resource *res = NULL;
310 void __iomem *start_ptr = NULL;
311 uint32_t rbcpr_start_addr = 0;
312 char *key = NULL;
313 uint32_t start_addr;
314
315 rbcpr_data = devm_kzalloc(&pdev->dev,
316 sizeof(struct rbcpr_data_type), GFP_KERNEL);
317
318 if (!rbcpr_data)
319 return -ENOMEM;
320
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600321 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
322
323 if (!res) {
324 pr_err("%s: Failed to get IO resource from platform device",
325 __func__);
326 ret = -ENXIO;
327 goto rbcpr_probe_fail;
328 }
329
330 key = "qcom,start-offset";
331 ret = of_property_read_u32(pdev->dev.of_node, key, &start_addr);
332
333 if (ret) {
334 pr_err("%s: Failed to get start offset", __func__);
335 goto rbcpr_probe_fail;
336 }
337
338 start_addr += res->start;
339 start_ptr = ioremap_nocache(start_addr, 4);
340
341 if (!start_ptr) {
342 pr_err("%s: Failed to remap RBCPR start pointer",
343 __func__);
344 goto rbcpr_probe_fail;
345 }
346
347 rbcpr_start_addr = res->start + readl_relaxed(start_ptr);
348
349 if ((rbcpr_start_addr > (res->end - RBCPR_STATS_MAX_SIZE)) ||
350 (rbcpr_start_addr < start_addr)) {
351 pr_err("%s: Invalid start address for rbcpr stats 0x%x",
352 __func__, rbcpr_start_addr);
353 goto rbcpr_probe_fail;
354 }
355
356 rbcpr_data->start = devm_ioremap_nocache(&pdev->dev, rbcpr_start_addr,
357 RBCPR_STATS_MAX_SIZE);
358
359 if (!rbcpr_data->start) {
360 pr_err("%s: Failed to remap RBCPR start address",
361 __func__);
362 goto rbcpr_probe_fail;
363 }
364
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530365 ret = msm_rpmrbcpr_validate(pdev);
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600366
367 if (ret)
368 goto rbcpr_probe_fail;
369
370 dent = debugfs_create_file("rpm_rbcpr", S_IRUGO, NULL,
Venkat Devarasettyb31780f2013-10-08 18:33:53 +0530371 rbcpr_data, &msm_rpmrbcpr_fops);
Girish Mahadevanb0ef7d52013-05-30 15:53:48 -0600372
373 if (!dent) {
374 pr_err("%s: error debugfs_create_file failed\n", __func__);
375 ret = -ENOMEM;
376 goto rbcpr_probe_fail;
377 }
378
379 platform_set_drvdata(pdev, dent);
380rbcpr_probe_fail:
381 iounmap(start_ptr);
382 return ret;
383}
384
385static int __devexit msm_rpmrbcpr_remove(struct platform_device *pdev)
386{
387 struct dentry *dent;
388
389 dent = platform_get_drvdata(pdev);
390 debugfs_remove(dent);
391 platform_set_drvdata(pdev, NULL);
392 return 0;
393}
394
395static struct of_device_id rpmrbcpr_stats_table[] = {
396 {.compatible = "qcom,rpmrbcpr-stats"},
397 {},
398};
399
400static struct platform_driver msm_rpmrbcpr_driver = {
401 .probe = msm_rpmrbcpr_probe,
402 .remove = __devexit_p(msm_rpmrbcpr_remove),
403 .driver = {
404 .name = "msm_rpmrbcpr_stats",
405 .owner = THIS_MODULE,
406 .of_match_table = rpmrbcpr_stats_table,
407 },
408};
409
410static int __init msm_rpmrbcpr_init(void)
411{
412 return platform_driver_register(&msm_rpmrbcpr_driver);
413}
414
415static void __exit msm_rpmrbcpr_exit(void)
416{
417 platform_driver_unregister(&msm_rpmrbcpr_driver);
418}
419
420module_init(msm_rpmrbcpr_init);
421module_exit(msm_rpmrbcpr_exit);